SciPy.org's NumPy

Intro

NumPy extends lists in Python when we need to work with arrays of numbers, providing a higher performance (throught vectorization) and functionality (basically, Linear Algebra).


In [2]:
try:
    import numpy as np
except:
    !pip3 install numpy --user
    import numpy as np

Looking for information


In [3]:
np.lookfor("invert")


Search results for 'invert'
---------------------------
numpy.bitwise_not
    Compute bit-wise inversion, or bit-wise NOT, element-wise.
numpy.matrix.getI
    Returns the (multiplicative) inverse of invertible `self`.
numpy.in1d
    Test whether each element of a 1-D array is also present in a second array.
numpy.isin
    Calculates `element in test_elements`, broadcasting over `element` only.
numpy.transpose
    Permute the dimensions of an array.
numpy.linalg.inv
    Compute the (multiplicative) inverse of a matrix.
numpy.linalg.pinv
    Compute the (Moore-Penrose) pseudo-inverse of a matrix.
numpy.random.SFC64
    BitGenerator for Chris Doty-Humphrey's Small Fast Chaotic PRNG.
numpy.linalg.tensorinv
    Compute the 'inverse' of an N-dimensional array.
numpy.linalg.matrix_power
    Raise a square matrix to the (integer) power `n`.
  • Using IPython, remember that it's possible to use the tabulator to extend some command or to use a wildcard to get information about the numpy's stuff:

In [7]:
np.*?

Data types

NumPy can works (create, operate and I/O) with arrays containig:

  1. Signed and unsigned integers of 8, 16, 32 and 64 bits.
  2. Floating point numbers of 32 and 64 bits.
  3. Complex (floating point) numbers of 64 and 128 bits.
  4. Strings.

A word about efficiency

NumPy has been designed to be efficient when it use blocks of memory (possiblely contiguous) of constant size. In other words, when the structures are static.

Allocation (without initializing the data array)


In [73]:
A = np.empty(12)
print(A) # Uninitialized garbage values, possiblely at random.


[-0.00e+000 -0.00e+000  1.14e-322  0.00e+000  0.00e+000  0.00e+000
  0.00e+000  0.00e+000  0.00e+000  0.00e+000  0.00e+000  0.00e+000]

In [76]:
print(np.empty_like(A))


[-1.10e+001 -0.00e+000  1.14e-322  0.00e+000  0.00e+000  0.00e+000
  0.00e+000  0.00e+000  0.00e+000  0.00e+000  0.00e+000  0.00e+000]

Allocating and initalizing

Using a list


In [ ]:
l = [1, 2, 3]
type(l)

In [30]:
A = np.array(l)
A


[1 2 3]

In [38]:
A = np.array([[1,1.0],(1+1j,.3)])
print(A)


[[1. +0.j 1. +0.j]
 [1. +1.j 0.3+0.j]]

Using "initializers"


In [3]:
np.zeros(10)


Out[3]:
array([0., 0., 0., 0., 0., 0., 0., 0., 0., 0.])

In [4]:
np.zeros((5,5))


Out[4]:
array([[0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0.],
       [0., 0., 0., 0., 0.]])

In [32]:
np.ones(10)


[1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]

In [24]:
np.full((5,5), 2)


[[2 2 2 2 2]
 [2 2 2 2 2]
 [2 2 2 2 2]
 [2 2 2 2 2]
 [2 2 2 2 2]]

In [34]:
np.arange(10)


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

In [35]:
np.linspace(1., 4., 6)


[1.  1.6 2.2 2.8 3.4 4. ]

In [36]:
np.random.rand(10)


[0.25947045 0.60669705 0.70614822 0.45711567 0.69540743 0.33478637
 0.91928435 0.86523359 0.2590883  0.46067252]

In [51]:
# The random number generator is not reset in each call
print(np.random.random((5,5)))
print(np.random.random((5,5)))


[[0.14515242 0.87567159 0.78860852 0.08344575 0.95831436]
 [0.43688972 0.54364482 0.44516617 0.89146911 0.57689682]
 [0.48386201 0.53665991 0.12748962 0.15890215 0.07821322]
 [0.9210016  0.19761478 0.99985553 0.2182543  0.23117302]
 [0.93475207 0.07723802 0.39685893 0.74970228 0.3775947 ]]
[[0.41814883 0.06939196 0.50654056 0.39892154 0.75983793]
 [0.83167939 0.99611236 0.89077388 0.90083662 0.96660508]
 [0.27428386 0.16960216 0.65274279 0.02139508 0.1638928 ]
 [0.49956801 0.39162647 0.57359619 0.07605081 0.78190786]
 [0.22709869 0.71448082 0.8691687  0.77746539 0.87666355]]

In [25]:
print(np.eye(5)) # Identity matrix


[[1. 0. 0. 0. 0.]
 [0. 1. 0. 0. 0.]
 [0. 0. 1. 0. 0.]
 [0. 0. 0. 1. 0.]
 [0. 0. 0. 0. 1.]]

In [29]:
A = np.array([i for i in range(5)]) # 1-D list comprehension
print(A, A[1], A.shape)


[0 1 2 3 4] 1 (5,)

In [30]:
A = np.array([[j+i*4 for j in range(4)] for i in range(5)]) # 2-D list comprehension
print(A, A.shape)


[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]
 [16 17 18 19]] (5, 4)

Defining types


In [12]:
A = np.array([1, 2], dtype=np.uint8)
A


Out[12]:
array([1, 2], dtype=uint8)

In [13]:
type(A[0])


Out[13]:
numpy.uint8

In [119]:
A = np.array([[1,2,3],[4,5,6]])
print(A)
print(A.shape)


[[1 2 3]
 [4 5 6]]
(2, 3)

In [120]:
np.reshape(A, (3, 2))


Out[120]:
array([[1, 2],
       [3, 4],
       [5, 6]])

C vs Fortran order


In [108]:
A = np.empty((5,4), dtype=np.int16, order='C') # C order is the default
for row in range(A.shape[0]):
    for col in range(A.shape[1]):
        A[row, col] = col+row*A.shape[1]
print(A)


[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]
 [16 17 18 19]]

In [103]:
print(np.isfortran(A))


False

In [116]:
B = np.reshape(A, (4,5), order='F')
print(B)


[[ 0 16 13 10  7]
 [ 4  1 17 14 11]
 [ 8  5  2 18 15]
 [12  9  6  3 19]]

In [117]:
print(np.isfortran(B))


True

In [118]:
print(A.flags)
print(B.flags)


  C_CONTIGUOUS : True
  F_CONTIGUOUS : False
  OWNDATA : True
  WRITEABLE : True
  ALIGNED : True
  WRITEBACKIFCOPY : False
  UPDATEIFCOPY : False

  C_CONTIGUOUS : False
  F_CONTIGUOUS : True
  OWNDATA : False
  WRITEABLE : True
  ALIGNED : True
  WRITEBACKIFCOPY : False
  UPDATEIFCOPY : False

Dynamic creation

Be careful, dynamic arrays in NumPy are slower than static ones.

  • Creating an empty (unallocated) array for working with 8-bits unsigned integers:

In [9]:
A = np.array([], dtype=np.uint8)
print(A, A.dtype)
A


[] uint8
Out[9]:
array([], dtype=uint8)

In [11]:
A = np.concatenate((A, np.array([1, 2], dtype=np.uint8)))
A


Out[11]:
array([1, 2], dtype=uint8)

(see NumPy's data types)

  • NumPy arrays are objects of the "numpy.ndarray" class:

In [252]:
print(type(A))


<class 'numpy.ndarray'>
  • All arrays has at least one dimension and a length in each dimenssion.

In [28]:
print(f"number of dimensions={A.ndim}, shape={A.shape}")


number of dimensions=1, shape=(0,)

Pointer copy

  • Simple assignments do not copy of array objects or of their data:

In [121]:
# https://stackoverflow.com/questions/56090021/list-comprehension-python-prime-numbers
Primes_less_than_100 = np.array([x for x in range(2,100) if not any([x % y == 0 for y in range(2, int(x/2)+1)])])
Primes_less_than_100


Out[121]:
array([ 2,  3,  5,  7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59,
       61, 67, 71, 73, 79, 83, 89, 97])

In [122]:
A = Primes_less_than_100 # This is a copy of pointers
A


Out[122]:
array([ 2,  3,  5,  7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59,
       61, 67, 71, 73, 79, 83, 89, 97])

In [123]:
A[0]=1
A


Out[123]:
array([ 1,  3,  5,  7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59,
       61, 67, 71, 73, 79, 83, 89, 97])

In [124]:
Primes_less_than_100


Out[124]:
array([ 1,  3,  5,  7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59,
       61, 67, 71, 73, 79, 83, 89, 97])

In [125]:
id(A)


Out[125]:
4678855056

In [126]:
id(Primes_less_than_100)


Out[126]:
4678855056

In [128]:
A is Primes_less_than_100


Out[128]:
True

Shallow copy (view)


In [129]:
print(Primes_less_than_100.shape)


(25,)

In [130]:
A = Primes_less_than_100.reshape(5,5)
print(A.shape)


(5, 5)

In [131]:
A is Primes_less_than_100


Out[131]:
False

In [132]:
A.base is Primes_less_than_100


Out[132]:
True

In [133]:
A.flags.owndata


Out[133]:
False

In [135]:
print(Primes_less_than_100, A)


[ 1  3  5  7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89
 97] [[ 1  3  5  7 11]
 [13 17 19 23 29]
 [31 37 41 43 47]
 [53 59 61 67 71]
 [73 79 83 89 97]]

In [138]:
A[0,0] = 2
print(Primes_less_than_100, A)


[ 2  3  5  7 11 13  2 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89
 97] [[ 2  3  5  7 11]
 [13  2 19 23 29]
 [31 37 41 43 47]
 [53 59 61 67 71]
 [73 79 83 89 97]]

Deep (object + data) copy


In [55]:
Primes_less_than_100 = np.array([x for x in range(2,100) if not any([x % y == 0 for y in range(2, int(x/2)+1)])])
A = np.copy(Primes_less_than_100)

In [139]:
A is Primes_less_than_100


Out[139]:
False

In [58]:
Primes_less_than_100[0]


Out[58]:
2

In [59]:
A[0] = 1

In [60]:
print(Primes_less_than_100[0], A[0])


2 1

In [61]:
timeit A=Primes_less_than_100 # This is much faster, depending on the size of the array


74.8 ns ± 6.22 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

In [62]:
%timeit A = np.copy(Primes_less_than_100)


5.77 µs ± 1.04 µs per loop (mean ± std. dev. of 7 runs, 100000 loops each)

More about array indexing and iterating

NumPy arrays are indexed by nonnegative integers.

More about simple indexing


In [82]:
A = np.arange(20).reshape(5, 4)
print(A)
print(A[1, 2]) # [row, column]


[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]
 [16 17 18 19]]
6

In [83]:
print(A[1]) # Get the second row


[4 5 6 7]

In [32]:
A[1][2] # [row][column]. Access first to the row, and then, to the column


Out[32]:
6
  • Be careful:

In [34]:
timeit A[1][2]


1.22 µs ± 70.8 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [35]:
timeit A[1,2] # Access directly to the element


503 ns ± 92.4 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

Array indexing (also known as fancy indexing)


In [156]:
A = np.arange(20).reshape(5, 4)
print(A)
dim_0_coordinates = [0, 1, 2]
dim_1_coordinates = [3, 2, 1]
print(A[dim_0_coordinates, dim_1_coordinates])


[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]
 [16 17 18 19]]
[3 6 9]
  • Notice that the indices can be determined by lists:

In [91]:
print(A[[i for i in range(3)], [i for i in range(3,0,-1)]])


[3 6 9]
  • ... or by NumPy arrays:

In [38]:
print(np.arange(3))
print(np.arange(3,0,-1))
print(A[np.arange(3), np.arange(3,0,-1)])


[0 1 2]
[3 2 1]
[3 6 9]

In [178]:
A = np.arange(100).reshape(10,10)
print(A)
lst_of_rows = [1, 2, 4]
lst_of_columns = [1, 2, 5]
sub_matrix = A[lst_of_rows][:, lst_of_columns]
print(sub_matrix)


[[ 0  1  2  3  4  5  6  7  8  9]
 [10 11 12 13 14 15 16 17 18 19]
 [20 21 22 23 24 25 26 27 28 29]
 [30 31 32 33 34 35 36 37 38 39]
 [40 41 42 43 44 45 46 47 48 49]
 [50 51 52 53 54 55 56 57 58 59]
 [60 61 62 63 64 65 66 67 68 69]
 [70 71 72 73 74 75 76 77 78 79]
 [80 81 82 83 84 85 86 87 88 89]
 [90 91 92 93 94 95 96 97 98 99]]
[[11 12 15]
 [21 22 25]
 [41 42 45]]
  • Be careful, advanced indexing always returns a copy of the data (contrast with basic slicing that returns a view).

In [13]:
B = A[[0, 1, 2], [0, 1, 2]]
print(B)


[ 0 11 22]

In [8]:
print(A)


[[ 0  1  2  3  4  5  6  7  8  9]
 [10 11 12 13 14 15 16 17 18 19]
 [20 21 22 23 24 25 26 27 28 29]
 [30 31 32 33 34 35 36 37 38 39]
 [40 41 42 43 44 45 46 47 48 49]
 [50 51 52 53 54 55 56 57 58 59]
 [60 61 62 63 64 65 66 67 68 69]
 [70 71 72 73 74 75 76 77 78 79]
 [80 81 82 83 84 85 86 87 88 89]
 [90 91 92 93 94 95 96 97 98 99]]

In [18]:
B[...] = -1

In [19]:
print(B)


[-1 -1 -1]

In [20]:
print(A)


[[ 0  1  2  3  4  5  6  7  8  9]
 [10 11 12 13 14 15 16 17 18 19]
 [20 21 22 23 24 25 26 27 28 29]
 [30 31 32 33 34 35 36 37 38 39]
 [40 41 42 43 44 45 46 47 48 49]
 [50 51 52 53 54 55 56 57 58 59]
 [60 61 62 63 64 65 66 67 68 69]
 [70 71 72 73 74 75 76 77 78 79]
 [80 81 82 83 84 85 86 87 88 89]
 [90 91 92 93 94 95 96 97 98 99]]

Boolean array indexing

  • Finding the elements bigger than ...

In [308]:
A = np.arange(20)
print(A, A.shape)
bool_idx = (A>12)
print(bool_idx, bool_idx.shape)


[ 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19] (20,)
[False False False False False False False False False False False False
 False  True  True  True  True  True  True  True] (20,)
  • Printing the elements bigger than ...

In [309]:
print(A[bool_idx])


[13 14 15 16 17 18 19]
  • Getting the elements of an array smaller than ...:

In [344]:
A = (100*(0.5-np.random.rand(25))).astype(np.int16).reshape(5,5)
print(A)
print(A[A<0]) # Notice that len(A[A<0]) <= len(A)


[[ 14 -31  30 -20  29]
 [ -8 -44 -48  32  -4]
 [ 33  26 -35  27  19]
 [ -3  48  44  11  -5]
 [-26 -35   1   9   9]]
[-31 -20  -8 -44 -48  -4 -35  -3  -5 -26 -35]
  • Changing the elements smaller than ...:

In [345]:
A[A<0] = 0
print(A)


[[14  0 30  0 29]
 [ 0  0  0 32  0]
 [33 26  0 27 19]
 [ 0 48 44 11  0]
 [ 0  0  1  9  9]]

Iterating


In [157]:
for row in A:
    print(row)


[0 1 2 3]
[4 5 6 7]
[ 8  9 10 11]
[12 13 14 15]
[16 17 18 19]

In [159]:
for element in A.flat:
    print(element, end=' ')


0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 

Extending arrays

  • Appending elements:

In [141]:
A = np.arange(3)
print(A)
A = np.append(A, 4)
print(A)


[0 1 2]
[0 1 2 4]

In [142]:
A = np.append(-1, A)
print(A)


[-1  0  1  2  4]

In [145]:
B = np.concatenate((A, A), axis=None)
print(B)


[-1  0  1  2  4 -1  0  1  2  4]

In [146]:
B = np.concatenate((A, A), axis=0)
print(B)


[-1  0  1  2  4 -1  0  1  2  4]

In [148]:
A = np.arange(30).reshape(5,6)
print(A)


[[ 0  1  2  3  4  5]
 [ 6  7  8  9 10 11]
 [12 13 14 15 16 17]
 [18 19 20 21 22 23]
 [24 25 26 27 28 29]]

In [150]:
B = np.ones(5, dtype=np.int16).reshape(5,1)
print(B)


[[1]
 [1]
 [1]
 [1]
 [1]]

In [153]:
C = np.hstack((A, B))
print(C)


[[ 0  1  2  3  4  5  1]
 [ 6  7  8  9 10 11  1]
 [12 13 14 15 16 17  1]
 [18 19 20 21 22 23  1]
 [24 25 26 27 28 29  1]]

In [154]:
B = np.ones(7, dtype=np.int16)
print(B)


[1 1 1 1 1 1 1]

In [155]:
C = np.vstack((C, B))
print(C)


[[ 0  1  2  3  4  5  1]
 [ 6  7  8  9 10 11  1]
 [12 13 14 15 16 17  1]
 [18 19 20 21 22 23  1]
 [24 25 26 27 28 29  1]
 [ 1  1  1  1  1  1  1]]

Permuting (swapping) dimensions (axes)

  • Permuting dimensions only makes sense when the number of dimensions is > 1:

In [178]:
A = np.arange(20).reshape(5,4)
print(A, A.shape)


[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]
 [12 13 14 15]
 [16 17 18 19]] (5, 4)

In [180]:
print(np.transpose(A), np.transpose(A).shape)


[[ 0  4  8 12 16]
 [ 1  5  9 13 17]
 [ 2  6 10 14 18]
 [ 3  7 11 15 19]] (4, 5)

In [179]:
print(A.T, A.T.shape)


[[ 0  4  8 12 16]
 [ 1  5  9 13 17]
 [ 2  6 10 14 18]
 [ 3  7 11 15 19]] (4, 5)
  • Transposing permutes all the dimensions:

In [183]:
A = np.arange(60).reshape(3,5,4)
print(A, A.shape)


[[[ 0  1  2  3]
  [ 4  5  6  7]
  [ 8  9 10 11]
  [12 13 14 15]
  [16 17 18 19]]

 [[20 21 22 23]
  [24 25 26 27]
  [28 29 30 31]
  [32 33 34 35]
  [36 37 38 39]]

 [[40 41 42 43]
  [44 45 46 47]
  [48 49 50 51]
  [52 53 54 55]
  [56 57 58 59]]] (3, 5, 4)

In [186]:
print(A.T, A.T.shape)


[[[ 0 20 40]
  [ 4 24 44]
  [ 8 28 48]
  [12 32 52]
  [16 36 56]]

 [[ 1 21 41]
  [ 5 25 45]
  [ 9 29 49]
  [13 33 53]
  [17 37 57]]

 [[ 2 22 42]
  [ 6 26 46]
  [10 30 50]
  [14 34 54]
  [18 38 58]]

 [[ 3 23 43]
  [ 7 27 47]
  [11 31 51]
  [15 35 55]
  [19 39 59]]] (4, 5, 3)

Increasing and decreasing dimensions

  • Shape and dimensions:

In [192]:
A = np.arange(5)
print(A, A.shape, A.ndim)


[0 1 2 3 4] (5,) 1
  • Increasing the dimensions on the right:

In [195]:
B = A[:, None]
print(B, B.shape, B.ndim)


[[0]
 [1]
 [2]
 [3]
 [4]] (5, 1) 2
  • Increasing the dimensions on the left:

In [194]:
B = A[None, :]
print(B, B.shape, B.ndim)


[[0 1 2 3 4]] (1, 5) 2
  • For convenience, NumPy provides the np.newaxis object instead None (althougt both are quivalent):

In [399]:
B = A[np.newaxis, :]
print(B, B.shape, B.ndim)


[[[[ 0  1  2]
   [ 3  4  5]
   [ 6  7  8]]

  [[ 9 10 11]
   [12 13 14]
   [15 16 17]]

  [[18 19 20]
   [21 22 23]
   [24 25 26]]]] (1, 3, 3, 3) 4

Slicing


In [160]:
A = np.arange(10)
print(A)


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

In [162]:
print(A[1:3]) # [start:end] (end not included)


[1 2]

In [163]:
print(A[1:5:2]) # [start:end:step]


[1 3]

In [164]:
print(A[:3]) # By default, the first one


[0 1 2]

In [165]:
print(A[3:])


[3 4 5 6 7 8 9]

In [168]:
print(A[::3])


[0 3 6 9]

In [170]:
print(A, A[:], A[::])


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

In [171]:
print(A[::-1])


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

In [286]:
A = np.arange(50).reshape(5,10)
print(A)
print(A[:]) # <- works, although it is preferable ...
print(A[:,:]) # <- ... this


[[ 0  1  2  3  4  5  6  7  8  9]
 [10 11 12 13 14 15 16 17 18 19]
 [20 21 22 23 24 25 26 27 28 29]
 [30 31 32 33 34 35 36 37 38 39]
 [40 41 42 43 44 45 46 47 48 49]]
[[ 0  1  2  3  4  5  6  7  8  9]
 [10 11 12 13 14 15 16 17 18 19]
 [20 21 22 23 24 25 26 27 28 29]
 [30 31 32 33 34 35 36 37 38 39]
 [40 41 42 43 44 45 46 47 48 49]]
[[ 0  1  2  3  4  5  6  7  8  9]
 [10 11 12 13 14 15 16 17 18 19]
 [20 21 22 23 24 25 26 27 28 29]
 [30 31 32 33 34 35 36 37 38 39]
 [40 41 42 43 44 45 46 47 48 49]]

In [299]:
print(A[0:2,:])


[[ 0  1  2  3  4  5  6  7  8  9]
 [10 11 12 13 14 15 16 17 18 19]]

In [298]:
print(A[:,1::2])


[[ 1  3  5  7  9]
 [11 13 15 17 19]
 [21 23 25 27 29]
 [31 33 35 37 39]
 [41 43 45 47 49]]

In [300]:
print(A[A.shape[0]-2:,A.shape[1]-2:]) # bottom-right 2x2 array:


[[38 39]
 [48 49]]

In [304]:
print(A[2]) # Row extraction
print(A[2:3,:]) # Sub-matrix extraction


[20 21 22 23 24 25 26 27 28 29]
[[20 21 22 23 24 25 26 27 28 29]]
  • Slices are views of the same data:

In [299]:
A = np.arange(10)
print(A)
B = A[1:3] # B is simply a new view of A
B[:] = 1000
print(B)
print(A)


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

In [300]:
A = np.arange(10)
print(A)
B = A[::-1]
B[1] = 1000
print(B)
print(A)


[0 1 2 3 4 5 6 7 8 9]
[   9 1000    7    6    5    4    3    2    1    0]
[   0    1    2    3    4    5    6    7 1000    9]
  • Copying slices:

In [301]:
A = np.arange(10)
print(A)
B = A[::-1].copy()
B[1] = 1000
print(B)
print(A)


[0 1 2 3 4 5 6 7 8 9]
[   9 1000    7    6    5    4    3    2    1    0]
[0 1 2 3 4 5 6 7 8 9]
  • Ellipsis:

In [396]:
A = np.arange(27).reshape(3,3,3)
print(A)


[[[ 0  1  2]
  [ 3  4  5]
  [ 6  7  8]]

 [[ 9 10 11]
  [12 13 14]
  [15 16 17]]

 [[18 19 20]
  [21 22 23]
  [24 25 26]]]

In [397]:
print(A[1,:,:])


[[ 9 10 11]
 [12 13 14]
 [15 16 17]]

In [398]:
print(A[1,...])


[[ 9 10 11]
 [12 13 14]
 [15 16 17]]

Mathematics

Some school math


In [187]:
A = np.random.randint(low=-1, high=+2, size=10)
print(A)
print(-A)


[ 0  0  1  1  0 -1  1  0 -1  0]
[ 0  0 -1 -1  0  1 -1  0  1  0]

In [194]:
B = np.random.randint(low=-1, high=+2, size=10)
print(A)
print(B)
print("-"*31)
print(A+B)


[ 0  0  1  1  0 -1  1  0 -1  0]
[ 1  1  0  1  0  1 -1  0 -1  0]
-------------------------------
[ 1  1  1  2  0  0  0  0 -2  0]

In [198]:
print(A*B)
print(A/B)
print(A//2)
print(A>>1)
print(A*2)
print(A**2)
print(1/A)
print(np.absolute(A))


[ 0  0  0  1  0 -1 -1  0  1  0]
[ 0.  0. inf  1. nan -1. -1. nan  1. nan]
[ 0  0  0  0  0 -1  0  0 -1  0]
[ 0  0  0  0  0 -1  0  0 -1  0]
[ 0  0  2  2  0 -2  2  0 -2  0]
[0 0 1 1 0 1 1 0 1 0]
[inf inf  1.  1. inf -1.  1. inf -1. inf]
[0 0 1 1 0 1 1 0 1 0]
/usr/local/lib/python3.7/site-packages/ipykernel_launcher.py:2: RuntimeWarning: divide by zero encountered in true_divide
  
/usr/local/lib/python3.7/site-packages/ipykernel_launcher.py:2: RuntimeWarning: invalid value encountered in true_divide
  
/usr/local/lib/python3.7/site-packages/ipykernel_launcher.py:7: RuntimeWarning: divide by zero encountered in true_divide
  import sys

Some measurements


In [200]:
A = np.arange(10)
print(A)


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

In [232]:



12.61599928712066

In [203]:
print("Sum =", np.sum(A))
print("Max =", np.max(A))
print("Min =", np.min(A))
print("L0 norm =", np.linalg.norm(A, ord=0)) # np.max(A)
print("L1 norm =", np.linalg.norm(A, ord=1)) # np.sum(A)
print("L2 norm =", np.linalg.norm(A)) # math.sqrt(sum(A_i**2 for A_i in A))
print("L4 norm =", np.linalg.norm(A, ord=4))


Sum = 45
Max = 9
Min = 0
L0 norm = 9.0
L1 norm = 45.0
L2 norm = 16.881943016134134

Some vector math


In [248]:
from IPython.display import display, Math
A = np.arange(10)
print(A)
B = A[::-1]
display(Math(r"A \cdot B={}".format(np.dot(A, B))))
print("A dot product B =", np.dot(A, B))
print("sum(A_i*B_i for A_i, B_i in zip(A, B))=", sum(A_i*B_i for A_i, B_i in zip(A, B)))
print("sum(A[:]*B[:]) =", sum(A[:]*B[:]))


[0 1 2 3 4 5 6 7 8 9]
$\displaystyle A \cdot B=120$
A dot product B = 120
sum(A_i*B_i for A_i, B_i in zip(A, B))= 120
sum(A[:]*B[:]) = 120

In [252]:
A = np.array([1, 2, 3])
B = np.array([-1, -2, -3])
display(Math(r"A \times B={}".format(np.cross(A, B))))
# https://stackoverflow.com/questions/1984799/cross-product-of-two-vectors-in-python
print(f"[A[1]*B[2] - A[2]*B[1], A[2]*B[0] - A[0]*B[2], A[0]*B[1] - A[1]*B[0]] = [{A[1]*B[2] - A[2]*B[1]} {A[2]*B[0] - A[0]*B[2]} {A[0]*B[1] - A[1]*B[0]}]")


$\displaystyle A \times B=[0 0 0]$
[A[1]*B[2] - A[2]*B[1], A[2]*B[0] - A[0]*B[2], A[0]*B[1] - A[1]*B[0]] = [0 0 0]

In [255]:
A = np.arange(4).reshape(2,2)
print(A)
B = A[::-1]
print(B)
print("A dot product B = ", np.dot(A, B)) # dot[i,j] = sum(A[i,:] * B[:,j])
# https://stackoverflow.com/questions/11033573/difference-between-numpy-dot-and-inner
for i in range(A.shape[0]):
    for j in range(A.shape[1]):
        print(np.sum(A[i,:]*B[:,j]), end=" ")
    print("")
print("A inner product B =", np.inner(A, B)) # inner[i,j] = (A[i,:] * B[j,:])
for i in range(A.shape[0]):
    for j in range(A.shape[1]):
        print(np.sum(A[i,:]*B[j,:]), end=" ")
    print("")


[[0 1]
 [2 3]]
[[2 3]
 [0 1]]
A dot product B =  [[0 1]
 [4 9]]
0 1 
4 9 
A inner product B = [[ 3  1]
 [13  3]]
3 1 
13 3 

Some matrix math


In [213]:
A = np.array([[(i+j)%2 for j in range(10)] for i in range(10)])
print(A, A.shape)


[[0 1 0 1 0 1 0 1 0 1]
 [1 0 1 0 1 0 1 0 1 0]
 [0 1 0 1 0 1 0 1 0 1]
 [1 0 1 0 1 0 1 0 1 0]
 [0 1 0 1 0 1 0 1 0 1]
 [1 0 1 0 1 0 1 0 1 0]
 [0 1 0 1 0 1 0 1 0 1]
 [1 0 1 0 1 0 1 0 1 0]
 [0 1 0 1 0 1 0 1 0 1]
 [1 0 1 0 1 0 1 0 1 0]] (10, 10)

In [101]:
B = np.array([[1] for i in range(10)])
print(B, B.shape)


[[1]
 [1]
 [1]
 [1]
 [1]
 [1]
 [1]
 [1]
 [1]
 [1]] (10, 1)

In [102]:
C = A @ B # Product matrix-matrix
print(C)


[[5]
 [5]
 [5]
 [5]
 [5]
 [5]
 [5]
 [5]
 [5]
 [5]]

In [106]:
print(C.T, C.T.shape, C.shape) # Transpose


[[5 5 5 5 5 5 5 5 5 5]] (1, 10) (10, 1)

In [214]:
print("Determinant =", np.linalg.det(A))


Determinant = 0.0

In [221]:
R = np.random.rand(5,5)
iR = np.linalg.inv(R)
print("Inverse =", np.linalg.inv(iR)) # Matrix inverse


Inverse = [[0.76317645 0.45952354 0.43724354 0.68357584 0.78271987]
 [0.27937175 0.72145406 0.79684589 0.26227242 0.98982401]
 [0.1900834  0.07878547 0.58703149 0.40637025 0.03645289]
 [0.73073553 0.12246182 0.22709368 0.82453272 0.41691559]
 [0.52055731 0.53846115 0.62722803 0.79181958 0.55535073]]

In [222]:
print(np.round(R @ iR))


[[ 1.  0.  0.  0.  0.]
 [ 0.  1.  0.  0.  0.]
 [ 0.  0.  1.  0.  0.]
 [-0.  0.  0.  1.  0.]
 [-0.  0.  0.  0.  1.]]

In [223]:
print(R @ iR)


[[ 1.00000000e+00  4.44089210e-16  0.00000000e+00  0.00000000e+00
   4.44089210e-16]
 [ 8.88178420e-16  1.00000000e+00  4.44089210e-16  0.00000000e+00
   4.44089210e-16]
 [ 1.66533454e-16  1.11022302e-16  1.00000000e+00  2.77555756e-17
   0.00000000e+00]
 [-2.22044605e-16  8.88178420e-16  2.22044605e-16  1.00000000e+00
   0.00000000e+00]
 [-4.44089210e-16  2.22044605e-16  5.55111512e-16  4.44089210e-16
   1.00000000e+00]]

In [224]:
print(np.round(iR @ R))


[[ 1.  0.  0.  0.  0.]
 [-0.  1.  0.  0.  0.]
 [-0.  0.  1. -0. -0.]
 [ 0. -0. -0.  1.  0.]
 [ 0.  0.  0.  0.  1.]]

In [225]:
print(iR @ R)


[[ 1.00000000e+00  6.66133815e-16  1.44328993e-15  6.66133815e-16
   8.88178420e-16]
 [-2.22044605e-16  1.00000000e+00  4.44089210e-16  0.00000000e+00
   0.00000000e+00]
 [-2.22044605e-16  0.00000000e+00  1.00000000e+00 -4.44089210e-16
  -1.38777878e-16]
 [ 2.22044605e-16 -2.22044605e-16 -4.44089210e-16  1.00000000e+00
   4.44089210e-16]
 [ 3.33066907e-16  2.22044605e-16  2.22044605e-16  4.44089210e-16
   1.00000000e+00]]

In [226]:
R = np.random.rand(5,4)
iR = np.linalg.pinv(R) # Pseudo-inverse
print(iR)


[[ 1.33558316  1.09717246 -0.87000433 -0.38723243 -0.86012088]
 [ 0.26385402  2.22255799 -0.53038992 -1.68906197 -0.74471528]
 [-1.10811207 -0.46044158  0.42505994  1.60139552  0.28686985]
 [ 0.55392441 -1.56309994  0.85608373  0.01126563  1.07211714]]

In [227]:
print(np.round(R @ iR))


[[ 1.  0. -0.  0.  0.]
 [ 0.  1.  0. -0. -0.]
 [-0.  0.  1.  0.  0.]
 [ 0. -0.  0.  1. -0.]
 [ 0. -0.  0. -0.  0.]]

In [228]:
print(R @ iR)


[[ 0.99794701  0.00607694 -0.02965709  0.00218336  0.03357882]
 [ 0.00607694  0.98201198  0.08778628 -0.00646284 -0.09939476]
 [-0.02965709  0.08778628  0.57157974  0.03154038  0.48507275]
 [ 0.00218336 -0.00646284  0.03154038  0.99767799 -0.03571114]
 [ 0.03357882 -0.09939476  0.48507275 -0.03571114  0.45078328]]

In [231]:
print(np.round(iR @ R))


[[ 1.  0. -0.  0.]
 [-0.  1. -0. -0.]
 [ 0. -0.  1. -0.]
 [-0.  0. -0.  1.]]

In [232]:
print(iR @ R)


[[ 1.00000000e+00  1.66533454e-16 -5.55111512e-17  4.44089210e-16]
 [-4.16333634e-16  1.00000000e+00 -8.32667268e-16 -4.44089210e-16]
 [ 1.28369537e-16 -2.49800181e-16  1.00000000e+00 -3.60822483e-16]
 [-5.82867088e-16  1.11022302e-16 -3.33066907e-16  1.00000000e+00]]

Broadcasting

In vectorized operations, NumPy "extends" scalars and arrays with one of its dimensions equal to the size of the other(s) array(s).


In [256]:
A = np.array([1, 2, 3])
b = 1
print(A+b) # Scalars are broadcasted


[2 3 4]

In [257]:
B = [1]
print(A+B) # When possible, arrays are broadcasted in all possible dimensions


[2 3 4]

In [262]:
A = np.ones((5,3), dtype=np.int16)
print(A)
B = np.arange(3)
print(B)
print(A+B)  # Broadcasting in the axis 0


[[1 1 1]
 [1 1 1]
 [1 1 1]
 [1 1 1]
 [1 1 1]]
[0 1 2]
[[1 2 3]
 [1 2 3]
 [1 2 3]
 [1 2 3]
 [1 2 3]]

In [264]:
print(A)
B = np.arange(5).reshape((5, 1))
print(B)
print(A+B)  # Broadcasting in the axis 1


[[1 1 1]
 [1 1 1]
 [1 1 1]
 [1 1 1]
 [1 1 1]]
[[0]
 [1]
 [2]
 [3]
 [4]]
[[1 1 1]
 [2 2 2]
 [3 3 3]
 [4 4 4]
 [5 5 5]]
  • Two dimensions are compatible when they are equal, or one of them is 1. Otherwise a ValueError: frames are not aligned is thrown.

In [127]:
B = np.arange(4)[:, None]
print(B)


[[0]
 [1]
 [2]
 [3]]

In [128]:
print(A.shape)


(5, 3)

In [129]:
print(B.shape)


(4, 1)

In [130]:
try:
    A + B
except ValueError as e:
    print("ValueError exception: ", end='')
    if hasattr(e, 'message'):
        print(e.message)
    else:
        print(e)


ValueError exception: operands could not be broadcast together with shapes (5,3) (4,1) 

How fast is Numpy's array math?


In [266]:
A = np.array([[(i*10+j) for j in range(10)] for i in range(10)])
print(A, A.shape)


[[ 0  1  2  3  4  5  6  7  8  9]
 [10 11 12 13 14 15 16 17 18 19]
 [20 21 22 23 24 25 26 27 28 29]
 [30 31 32 33 34 35 36 37 38 39]
 [40 41 42 43 44 45 46 47 48 49]
 [50 51 52 53 54 55 56 57 58 59]
 [60 61 62 63 64 65 66 67 68 69]
 [70 71 72 73 74 75 76 77 78 79]
 [80 81 82 83 84 85 86 87 88 89]
 [90 91 92 93 94 95 96 97 98 99]] (10, 10)
  • Add B[] to all the rows of A[][] using scalar arithmetic:

In [137]:
C = np.empty_like(A)
def add():
    for i in range(A.shape[1]):
        for j in range(A.shape[0]):
            C[i, j] = A[i, j] + B[j]
%timeit add()
print(C)


125 µs ± 9.03 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
[[  0   2   4   6   8  10  12  14  16  18]
 [ 10  12  14  16  18  20  22  24  26  28]
 [ 20  22  24  26  28  30  32  34  36  38]
 [ 30  32  34  36  38  40  42  44  46  48]
 [ 40  42  44  46  48  50  52  54  56  58]
 [ 50  52  54  56  58  60  62  64  66  68]
 [ 60  62  64  66  68  70  72  74  76  78]
 [ 70  72  74  76  78  80  82  84  86  88]
 [ 80  82  84  86  88  90  92  94  96  98]
 [ 90  92  94  96  98 100 102 104 106 108]]
  • Add B[] to all the rows of B[][] using vectorial computation:

In [138]:
C = np.empty_like(A)
def add():
    for i in range(A.shape[1]):
        C[i, :] = A[i, :] + B
%timeit add()
print(C)


38.5 µs ± 779 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
[[  0   2   4   6   8  10  12  14  16  18]
 [ 10  12  14  16  18  20  22  24  26  28]
 [ 20  22  24  26  28  30  32  34  36  38]
 [ 30  32  34  36  38  40  42  44  46  48]
 [ 40  42  44  46  48  50  52  54  56  58]
 [ 50  52  54  56  58  60  62  64  66  68]
 [ 60  62  64  66  68  70  72  74  76  78]
 [ 70  72  74  76  78  80  82  84  86  88]
 [ 80  82  84  86  88  90  92  94  96  98]
 [ 90  92  94  96  98 100 102 104 106 108]]
  • Add B[] to all the rows of A[][] using fully vectorial computation:

In [139]:
%timeit C = A + B # <- broadcasting is faster
print(C)


4.05 µs ± 64.9 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
[[  0   2   4   6   8  10  12  14  16  18]
 [ 10  12  14  16  18  20  22  24  26  28]
 [ 20  22  24  26  28  30  32  34  36  38]
 [ 30  32  34  36  38  40  42  44  46  48]
 [ 40  42  44  46  48  50  52  54  56  58]
 [ 50  52  54  56  58  60  62  64  66  68]
 [ 60  62  64  66  68  70  72  74  76  78]
 [ 70  72  74  76  78  80  82  84  86  88]
 [ 80  82  84  86  88  90  92  94  96  98]
 [ 90  92  94  96  98 100 102 104 106 108]]

Arrays of objects

  • For example, an array of strings:

In [140]:
A = np.array(['hello', 'world!'])
print(A)
print(A.shape)
print(np.char.upper(A))


['hello' 'world!']
(2,)
['HELLO' 'WORLD!']
  • Simulating a dictionary:

In [321]:
A = np.array([("Spain", 100), ("France", 200), ("Italy", 300)])
print(A) # Notice that all the elements are srings
print(A.shape)
print(A[:,0])
print(A[A[:,0] == "France"])
print(A[A[:,0] == "France"][:,1])
print("The value associated to the key France is", A[A[:,0] == "France"][:,1][0])


[['Spain' '100']
 ['France' '200']
 ['Italy' '300']]
(3, 2)
['Spain' 'France' 'Italy']
[['France' '200']]
['200']
The value associated to the key France is 200
  • A dictionary is faster:

In [327]:
%timeit A[A[:,0] == "France"][:,1][0]
dictionary = {"Spain":100, "France":200, "Italy":300}
print(dictionary["France"])
%timeit dictionary["France"]


12.4 µs ± 559 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
200
68.4 ns ± 1.65 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
  • However, this difference can be smaller depending on the type of search:

In [337]:
others = [value for key, value in dictionary.items() if key != "France"]
print(others)
%timeit [value for key, value in dictionary.items() if key != "France"]


[100, 300]
1.12 µs ± 57.4 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

In [338]:
print(A[:,0] != "France")
print(A[A[:,0] != "France"])
print(A[A[:,0] != "France"][:,1])
print(A[A[:,0] != "France"][:,1].astype(np.int16))
%timeit A[A[:,0] != "France"][:,1].astype(np.int16)


[ True False  True]
[['Spain' '100']
 ['Italy' '300']]
['100' '300']
[100 300]
21.4 µs ± 1.24 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)

Structured arrays

  • Create a 1D array of (two) records, where each record has the structure (int, float, char[10]).

In [142]:
X = np.array([(1, 2., "Hello"), (3, 4., "World")],
             dtype=[("first", "i4"),("second", "f4"), ("third", "S10")])
# See https://docs.scipy.org/doc/numpy/reference/arrays.dtypes.html
print(X)


[(1, 2., b'Hello') (3, 4., b'World')]
  • Get the first element of every record:

In [143]:
print(X["first"])


[1 3]
  • Get the first record:

In [144]:
print(X[0])


(1, 2., b'Hello')
  • Get the second element of every record:

In [145]:
print(X["second"])


[2. 4.]
  • Third element of every record:

In [146]:
print(X["third"])


[b'Hello' b'World']

Disk I/O

  • Output data to an ASCII file:

In [147]:
Data = np.array([[1., 200.], [2., 150.], [3., 250.]])
np.savetxt("data.txt", Data)
!cat Data.txt


1.000000000000000000e+00 2.000000000000000000e+02
2.000000000000000000e+00 1.500000000000000000e+02
3.000000000000000000e+00 2.500000000000000000e+02
  • Input data from an ASCII file:

In [148]:
np.genfromtxt('data.txt')


Out[148]:
array([[  1., 200.],
       [  2., 150.],
       [  3., 250.]])
  • Output data to a binary file (using the native endianness):

In [149]:
ofile = open("data.float64", mode="wb")
Data.tofile(ofile)
  • Input data from a binary file (using the native endianness):

In [150]:
print(np.fromfile("data.float64", dtype=np.float64))


[  1. 200.   2. 150.   3. 250.]
  • Numpy and C use the same endianness:

In [151]:
!cat create_float64.c
!gcc create_float64.c -o create_float64
!./create_float64


#include <stdio.h>

#define N 10

int main() {
  double a[N];
  int i;
  FILE *ofile = fopen("data.float64", "wb");
  for(i=0; i<N; i++) {
    a[i] = i;
  }
  fwrite(a, sizeof(double), N, ofile);
  fclose(ofile);
  fprintf(stderr,"create_float64: done\n");
}
create_float64: done

In [152]:
print(np.fromfile("data.float64", dtype=np.float64))


[0. 1. 2. 3. 4. 5. 6. 7. 8. 9.]
  • Specifiying the endianness:

In [153]:
print(np.fromfile("data.float64", dtype=">d"))
# (> = bit-endian, d = double, see https://docs.scipy.org/doc/numpy/reference/arrays.dtypes.html)


[0.00000e+000 3.03865e-319 3.16202e-322 1.04347e-320 2.05531e-320
 2.56124e-320 3.06716e-320 3.57308e-320 4.07901e-320 4.33197e-320]
  • Make the things easier:

In [154]:
ofile = open("data.npy", mode="wb")
A = (100*np.random.rand(2,3)).astype(np.uint16)
print(A)


[[45 74 96]
 [86 78 94]]

In [155]:
np.save(ofile, A)

In [156]:
!ls save*


save_data

In [157]:
print(np.load("data.npy"))


[[45 74 96]
 [86 78 94]]

In [ ]:
print(A.dtype.byteorder)

Timming NumPy?

  • Lets define a list and compute the sum of its elements, timing it:

In [2]:
l = list(range(0,100000)); print(type(l), l[:10])
%timeit sum(l)


<class 'list'> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
1.54 ms ± 75.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
  • An now, lets create a numpy's array and time the sum of its elements:

In [3]:
A = np.arange(0, 100000); print(type(A), A[:10])
%timeit np.sum(A)


<class 'numpy.ndarray'> [0 1 2 3 4 5 6 7 8 9]
98.7 µs ± 8.7 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)
  • And what about a pure C implementation of an equivalent computation:

In [4]:
!cat sum_array.c
!gcc -O3 sum_array.c -o sum_array
%timeit !./sum_array


#include <stdio.h>
#include <time.h>
#include "sum_array_lib.c"

#define N 100000

int main() {
  double a[N];
  int i;
  clock_t start, end;
  double cpu_time;
  for(i=0; i<N; i++) {
    a[i] = i;
  }
  start = clock();
  double sum = sum_array(a,N);
  end = clock();
  printf("%f ", sum);
  cpu_time = ((double) (end - start)) / CLOCKS_PER_SEC;
  cpu_time *= 1000000;
  printf("%f usegs\n", cpu_time);
}
4999950000.000000 206.000000 usegs
4999950000.000000 150.000000 usegs
4999950000.000000 150.000000 usegs
4999950000.000000 150.000000 usegs
4999950000.000000 168.000000 usegs
4999950000.000000 171.000000 usegs
4999950000.000000 176.000000 usegs
4999950000.000000 195.000000 usegs
4999950000.000000 152.000000 usegs
4999950000.000000 166.000000 usegs
4999950000.000000 168.000000 usegs
4999950000.000000 173.000000 usegs
4999950000.000000 174.000000 usegs
4999950000.000000 173.000000 usegs
4999950000.000000 171.000000 usegs
4999950000.000000 183.000000 usegs
4999950000.000000 157.000000 usegs
4999950000.000000 167.000000 usegs
4999950000.000000 174.000000 usegs
4999950000.000000 189.000000 usegs
4999950000.000000 150.000000 usegs
4999950000.000000 174.000000 usegs
4999950000.000000 173.000000 usegs
4999950000.000000 167.000000 usegs
4999950000.000000 151.000000 usegs
4999950000.000000 171.000000 usegs
4999950000.000000 165.000000 usegs
4999950000.000000 190.000000 usegs
4999950000.000000 177.000000 usegs
4999950000.000000 167.000000 usegs
4999950000.000000 150.000000 usegs
4999950000.000000 190.000000 usegs
4999950000.000000 179.000000 usegs
4999950000.000000 183.000000 usegs
4999950000.000000 170.000000 usegs
4999950000.000000 173.000000 usegs
4999950000.000000 180.000000 usegs
4999950000.000000 175.000000 usegs
4999950000.000000 173.000000 usegs
4999950000.000000 169.000000 usegs
4999950000.000000 150.000000 usegs
4999950000.000000 166.000000 usegs
4999950000.000000 151.000000 usegs
4999950000.000000 150.000000 usegs
4999950000.000000 151.000000 usegs
4999950000.000000 212.000000 usegs
4999950000.000000 173.000000 usegs
4999950000.000000 177.000000 usegs
4999950000.000000 176.000000 usegs
4999950000.000000 169.000000 usegs
4999950000.000000 165.000000 usegs
4999950000.000000 150.000000 usegs
4999950000.000000 151.000000 usegs
4999950000.000000 182.000000 usegs
4999950000.000000 166.000000 usegs
4999950000.000000 152.000000 usegs
4999950000.000000 207.000000 usegs
4999950000.000000 166.000000 usegs
4999950000.000000 170.000000 usegs
4999950000.000000 165.000000 usegs
4999950000.000000 166.000000 usegs
4999950000.000000 253.000000 usegs
4999950000.000000 218.000000 usegs
4999950000.000000 166.000000 usegs
4999950000.000000 150.000000 usegs
4999950000.000000 266.000000 usegs
4999950000.000000 150.000000 usegs
4999950000.000000 171.000000 usegs
4999950000.000000 151.000000 usegs
4999950000.000000 150.000000 usegs
4999950000.000000 167.000000 usegs
4999950000.000000 151.000000 usegs
4999950000.000000 179.000000 usegs
4999950000.000000 166.000000 usegs
4999950000.000000 151.000000 usegs
4999950000.000000 151.000000 usegs
4999950000.000000 166.000000 usegs
4999950000.000000 150.000000 usegs
4999950000.000000 165.000000 usegs
4999950000.000000 151.000000 usegs
4999950000.000000 151.000000 usegs
133 ms ± 2.53 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
  • Another example:

In [5]:
# Example extracted from https://github.com/pyHPC/pyhpc-tutorial
lst = range(1000000)

for i in lst[:10]:
    print(i, end=' ')
print()

%timeit [i + 1 for i in lst] # A Python list comprehension (iteration happens in C but with PyObjects)
x = [i + 1 for i in lst]

print(x[:10])

arr = np.arange(1000000) # A NumPy list of integers
%timeit arr + 1 # Use operator overloading for nice syntax, now iteration is in C with ints
y = arr + 1

print(y[:10])


0 1 2 3 4 5 6 7 8 9 
207 ms ± 4.56 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
3 ms ± 283 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
[ 1  2  3  4  5  6  7  8  9 10]