NumPy - start

https://numpy.org/
NumPy. The fundamental package for scientific computing with Python.

https://www.python-course.eu/numpy.php
Numpy Tutorial

Charles R. Harris et al, Array programming with NumPy, Nature 585, 357-362 (2020).

Pauli Virtanen et al, SciPy 1.0: fundamental algorithms for scientific computing in Python, Nature Methods 17, 261-272 (2020).

INSTALLING NUMPY

https://numpy.org/install/


If you already have Python, you can install NumPy with:

ANACONDA
# Best practice, use an environment rather than install in the base env
conda create -n my-env
conda activate my-env
# If you want to install from conda-forge
conda config --env --add channels conda-forge
# The actual install command
conda install numpy

PIP (in a virtual environment)
pip install numpy

APT (Debian Linux)
Packages (with dependencies): python-numpy (Py2.7), python3-numpy (Py3),
python-pip, python-pip-whl, python3-pip.

INTRODUCTION

NumPy stands for Numerical Python. NumPy is the fundamental package for scientific computing with Python. It contains among other things [version 1.16.2 in Debian 10]:

NumPy’s main object is the homogeneous multidimensional array. It is a table of elements (usually numbers), all of the same type, indexed by a tuple of non-negative integers. In NumPy dimensions are called 'axes'.


import numpy as np   # typical import under the 'np' alias

print(np.__version__)   # 1.16.2 for Debian 10
# also help(np) at the end

a = np.arange(15).reshape(3, 5)   # array object 
# arange() is like range() but returns an array.
# np.arange( 10, 30, 5 )
# np.arange( 10, 30, 5, dtype=float )
# np.arange( 0, 2, 0.3 )   # it accepts float arguments

print(a)
# [[ 0  1  2  3  4]
#  [ 5  6  7  8  9]
#  [10 11 12 13 14]]
# The shape of an array is the number of elements in each dimension.

print(a.shape)   # (3, 5), tuple, np.shape(a)
print(a.ndim)   # 2
print(a.dtype)   # dtype('int64'), deduced from the sequence
print(a.itemsize)   # 8, size of bytes of each element of the array
print(a.size)   # 15, the number of items
print(a.nbytes)   # 120
assert a.nbytes == a.size * a.itemsize

MEMORY CONSUMPTION


lst = [24, 12, 57]
arr = np.array([24, 12, 57])

#   lst
#    |
#    o
# +------+-----+-----+-----+
# | info | ref | ref | ref | list object
# +------+--|--+--|--+--|--+ 64 + len(lst) * 8 bytes
#           |     |     |
#           o     o     o
#         +--+  +--+  +--+
#         |24|  |12|  |57|    len(lst) * 28 bytes
#         +--+  +--+  +--+
#         int   int   int
# Total: 64 + len(lst) * 8 + len(lst) * 28

#   arr
#    |
#    o
# +------+----+----+----+
# | info | 24 | 12 | 57 | array object
# +------+----+----+----+ 96 + len(arr) * 8 bytes
#         int  int  int

# Tests in Python 3 and with a 64-bit laptop.
import sys
import numpy as np

print(sys.getsizeof([]))           # 64, empty list
print(sys.getsizeof(np.array([])))   # 96, empty array

lst = [24, 12, 57]
print(sys.getsizeof(lst))   # 88 = 64 + 3 * 8, list object
print(sys.getsizeof(lst[0]))   # 28, int
# This is a minimum estimation, as Python integers can use more than 28 bytes.

arr = np.array([24, 12, 57])
print(arr.dtype)            # dtype('int64')
print(sys.getsizeof(arr))   # 120 = 96 + 3 * 8

arr = np.array([24, 12, 57], dtype="int8")   # numbers from -127 to 127
print(sys.getsizeof(arr))   # 99 = 96 + 3 * 1

lst[0] = "alpha"
print(sys.getsizeof(""))       # 49
print(sys.getsizeof(lst))   # 88, the same
print(sys.getsizeof(lst[0]))   # 54 = 49 + 5, str
print(sys.getsizeof(lst[1]))   # 28, int

# Numpy arrays can be like Python lists.

arr2 = np.array([1, 2.3, "word"], dtype=object)
sys.getsizeof(arr2)   # 120 = 96 + 3 * 8 (3 references)
sys.getsizeof(arr2[0])   # 28, int
sys.getsizeof(arr2[1])   # 24, float
sys.getsizeof(arr2[2])   # 53 = 49 + 4, str
# Total: 120 + 28 + 24 + 52 = 224 bytes

TIME COMPARISON


import timeit
import numpy as np

N = 1000000

x1 = list(range(N))
y1 = list(range(N))

x2 = np.arange(N)
y2 = np.arange(N)

print("Testing list comprehension ...")
t1 = timeit.Timer(lambda: [x1[i] + y1[i] for i in range(N)])
print(t1.timeit(1))   # single run

print("Testing zip ...")
t1 = timeit.Timer(lambda: [a+b for (a,b) in zip(x1,y1)])
print(t1.timeit(1))   # single run

print("Testing numpy ...")
t1 = timeit.Timer(lambda: x2 + y2)
print(t1.timeit(1))   # single run

# Results:
# Testing list comprehension ...
# 0.0699481050032773
# Testing zip ...
# 0.04706689599697711
# Testing numpy ...
# 0.0011762819995055906

# Warning!
x1 = list(range(0,120,20))   # [0, 20, 40, 60, 80, 100]
y1 = [100] * 6
[a+b for (a,b) in zip(x1,y1)]   # [100, 120, 140, 160, 180, 200]

x2 = np.arange(0,120,20, dtype="int8") # array([0, 20, 40, 60, 80, 100], dtype=int8)
y2 = np.array([100] * 6, dtype="int8") # array([100, 100, 100, 100, 100, 100], dtype=int8)
x2 + y2   # array([ 100, 120, -116, -96, -76, -56], dtype=int8) ERROR out of range
# Great power comes with great responsibility! (Peter Parker principle)