├── .gitignore ├── Chapter 01 ├── array-arithmetic-and-functions.py ├── basic-mathematical-functions.py ├── complex-type.py ├── decimal-type.py ├── eigenvalus-and-eigenvectors.py ├── fraction-type.py ├── matrices-and-n-dimensional-arrays.py ├── matrix-properties-of-numpy-arrays.py ├── numpy-arrays.py ├── solving-equations.py ├── sparse-matrices.py ├── systems-of-equations.py └── useful-array-creation-routines.py ├── Chapter 02 ├── adding-labels-and-legends-to-plots.py ├── adding-subplots.py ├── basic-plotting-with-matplotlib.py ├── changing-the-plotting-style.py ├── customising-3d-plots.py ├── saving-matplotlib-figures.py └── surface-and-contour-plots.py ├── Chapter 03 ├── discrete-fourier-transforms-for-signals-processing.py ├── numerical-integration.py ├── partial-differential-equations.py ├── polynomials-and-calculus.py ├── solving-equations.py ├── solving-simple-differential-equations-numerically.py ├── solving-systems-of-differential-equations.py └── symbolic-calculus-using-sympy.py ├── Chapter 04 ├── analysing-conversion-rates-with-bayesian-techniques.py ├── changing-the-random-number-generator.py ├── creating-random-samples-of-data.py ├── estimating-parameters-with-monte-carlo-simulations.py ├── generating-normally-distributed-random-numbers.py ├── generating-random-data.py ├── selecting-items-at-random.py └── working-with-random-processes.py ├── Chapter 05 ├── coloring-a-network.py ├── creating-directed-and-weighted-networks.py ├── creating-networks-in-python.py ├── finding-minimum-spanning-trees-and-dominating-sets.py ├── finding-shortest-paths.py ├── generating-the-adjacency-matrix-for-a-network.py ├── getting-the-basic-characteristics-of-networks.py ├── quantifying-clustering-in-a-network.py └── visualising-networks.py ├── Chapter 06 ├── creating-interactive-plots-with-Bokeh.py ├── creating-series-and-dataframes.py ├── getting-descriptive-statistics-from-dataframes.py ├── loading-and-storing-data-from-a-dataframe.py ├── manipulating-data-frames.py ├── plotting-data-from-a-DataFrame.py ├── testing-hypotheses-for-non-parametric-data.py ├── testing-hypotheses-using-ANOVA.py ├── testing-hypotheses-using-t-tests.py └── understanding-a-population-using-sampling.py ├── Chapter 07 ├── classifying-using-logarithmic-regression.py ├── forecasting-from-time-series-data-using-arima.py ├── forecasting-seasonal-data-with-arima.py ├── modelling-time-series-data-with-arma.py ├── tsdata.py ├── using-linear-regression.py ├── using-multilinear-regression.py └── using-prophet-to-model-time-series.py ├── Chapter 08 ├── computing-convex-hulls.py ├── constructing-bezier-curves.py ├── finding-edges-in-images.py ├── finding-interior-points.py ├── mandelbrot.png ├── swisscheese-grid-10411.csv ├── triangulating-polygonal-regions.py └── visualizing-two-dimensional-geometric-figures.py ├── Chapter 09 ├── analyzing-simple-two-player-games.py ├── computing-nash-equilibria.py ├── minimising-a-non-linear-system.py ├── minimising-simple-linear-systems.py ├── using-gradient-descent-methods.py └── using-least-squares-to-fit-a-curve-to-data.py ├── Chapter 10 ├── accelerating-code-with-cython │ ├── mandelbrot │ │ ├── __init__.py │ │ ├── cython_mandel.pyx │ │ ├── python_mandel.py │ │ └── setup.py │ └── run.py ├── accouting-for-uncertainty-in-calculations.py ├── distributing-computations-with-dask.py ├── keeping-track-of-units-with-pint.py ├── loading-and-storing-data-from-netcdf.py ├── sample.csv ├── sample.ipynb ├── validating-data.py ├── working-with-data-streams.py └── working-with-geographical-data.py ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | 3 | __pycache__/ 4 | .mypy_cache/ 5 | .pytest_cache/ 6 | .vscode/ 7 | 8 | -------------------------------------------------------------------------------- /Chapter 01/array-arithmetic-and-functions.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | 5 | arr_a = np.array([1, 2, 3, 4]) 6 | arr_b = np.array([1, 0, -3, 1]) 7 | arr_a + arr_b # array([2, 2, 0, 5]) 8 | arr_a - arr_b # array([0, 2, 6, 3]) 9 | arr_a * arr_b # array([ 1, 0, -9, 4]) 10 | arr_b / arr_a # array([ 1. , 0. , -1. , 0.25]) 11 | arr_b**arr_a # array([1, 0, -27, 1]) 12 | 13 | 14 | 15 | 16 | arr = np.array([1, 2, 3, 4]) 17 | new = 2*arr 18 | print(new) 19 | # [2, 4, 6, 8] -------------------------------------------------------------------------------- /Chapter 01/basic-mathematical-functions.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | 4 | math.sqrt(4) # 2.0 5 | 6 | theta = math.pi / 4 7 | math.cos(theta) # 0.7071067811865476 8 | math.sin(theta) # 0.7071067811865475 9 | math.tan(theta) # 0.9999999999999999 10 | 11 | math.asin(-1) # -1.5707963267948966 12 | math.acos(-1) # 3.141592653589793 13 | math.atan(1) # 0.7853981633974483 14 | 15 | math.log(10) # 2.302585092994046 16 | math.log(10, 10) # 1.0 17 | 18 | math.gamma(5) # 24.0 19 | math.erf(2) # 0.9953222650189527 20 | 21 | 22 | 23 | math.comb(5, 2) # 10 24 | math.factorial(5) # 120 25 | 26 | 27 | 28 | 29 | math.gcd(2, 4) # 2 30 | math.gcd(2, 3) # 1 31 | 32 | 33 | 34 | nums = [0.1]*10 # list containing 0.1 ten times 35 | sum(nums) # 0.9999999999999999 36 | math.fsum(nums) # 1.0 -------------------------------------------------------------------------------- /Chapter 01/complex-type.py: -------------------------------------------------------------------------------- 1 | z = 1 + 1j 2 | z + 2 # 3 + 1j 3 | z.conjugate() # 1 - 1j -------------------------------------------------------------------------------- /Chapter 01/decimal-type.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | from decimal import Decimal 4 | 5 | num1 = Decimal('1.1') 6 | num2 = Decimal('1.563') 7 | num1 + num2 # Decimal('2.663') 8 | 9 | from decimal import getcontext 10 | ctx = getcontext() 11 | num = Decimal('1.1') 12 | num**4 # Decimal('1.4641') 13 | ctx.prec = 4 # set new precision 14 | num**4 # Decimal('1.464') 15 | 16 | 17 | 18 | from decimal import localcontext 19 | num = Decimal("1.1") 20 | with localcontext() as ctx: 21 | ctx.prec = 2 22 | num**4 # Decimal('1.5') 23 | num**4 # Decimal('1.4641') 24 | 25 | -------------------------------------------------------------------------------- /Chapter 01/eigenvalus-and-eigenvectors.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | from numpy import linalg 4 | 5 | A = np.array([[3, -1, 4], [-1, 0, -1], [4, -1, 2]]) 6 | 7 | v, B = linalg.eig(A) 8 | 9 | i = 0 # first eigenvalue/eigenvector pair 10 | lambda0 = v[i] 11 | print(lambda0) 12 | # 6.823156164525971 13 | x0 = B[:, i] # ith column of B 14 | print(x0) 15 | # array([ 0.73271846, -0.20260301, 0.649672352]) 16 | 17 | linalg.norm(x0) # 1.0 - eigenvalues are normalised. 18 | 19 | 20 | lhs = A @ x0 21 | rhs = lambda0*x0 22 | linalg.norm(lhs - rhs) # 2.8445583831733384e-15 - very small. 23 | 24 | -------------------------------------------------------------------------------- /Chapter 01/fraction-type.py: -------------------------------------------------------------------------------- 1 | 2 | from fractions import Fraction 3 | num1 = Fraction(1, 3) 4 | num2 = Fraction(1, 7) 5 | num1 * num2 # Fraction(1, 21) 6 | -------------------------------------------------------------------------------- /Chapter 01/matrices-and-n-dimensional-arrays.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | mat = np.array([[1, 2], [3, 4]]) 4 | vec = np.array([1, 2]) 5 | 6 | mat.shape # (2, 2) 7 | vec.shape # (2,) 8 | 9 | 10 | mat.reshape(4,) 11 | # array([1, 2, 3, 4]) 12 | 13 | 14 | mat1 = [[1, 2], [3, 4]] 15 | mat2 = [[5, 6], [7, 8]] 16 | mat3 = [[9, 10], [11, 12]] 17 | 18 | arr_3d = np.array([mat1, mat2, mat3]) 19 | arr_3d.shape # (3, 2, 2) 20 | 21 | mat[0, 0] # 1 - top left element 22 | mat[1, 1] # 4 - bottom right element 23 | 24 | mat[:, 0] # array([1, 3]) 25 | 26 | 27 | -------------------------------------------------------------------------------- /Chapter 01/matrix-properties-of-numpy-arrays.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | np.eye(3) 5 | # array([[1., 0., 0.], 6 | # [0., 1., 0.], 7 | # [0., 0., 1.]]) 8 | 9 | 10 | 11 | mat = np.array([[1, 2], [3, 4]]) 12 | mat.transpose() 13 | # array([[1, 3], 14 | # [2, 4]]) 15 | mat.T 16 | # array([[1, 3], 17 | # [2, 4]]) 18 | 19 | 20 | A = np.array([[1, 2], [3, 4]]) 21 | A.trace() # 5 22 | 23 | A = np.array([[1, 2], [3, 4]]) 24 | B = np.array([[-1, 1], [0, 1]]) 25 | A @ B 26 | # array([[-1, 3], 27 | # [-3, 7]]) 28 | A * B 29 | # array([[-1, 2], 30 | # [ 0, 4]]) 31 | 32 | 33 | A = np.array([[1, 2], [3, 4]]) 34 | I = np.eye(2) 35 | A @ I 36 | # array([[1, 2], 37 | # [3, 4]]) 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | from numpy import linalg 47 | linalg.det(A) # -2.0000000000000004 48 | linalg.inv(A) 49 | # array([[-2. , 1. ], 50 | # [ 1.5, -0.5]]) 51 | 52 | 53 | 54 | 55 | Ainv = linalg.inv(A) 56 | Ainv @ A 57 | # Approximately 58 | # array([[1., 0.], 59 | # [0., 1.]]) 60 | 61 | A @ Ainv 62 | # Approximately 63 | # array([[1., 0.], 64 | # [0., 1.]]) 65 | 66 | -------------------------------------------------------------------------------- /Chapter 01/numpy-arrays.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | ary = np.array([1, 2, 3, 4]) # array([1, 2, 3, 4]) 4 | 5 | ary[0] # 1 6 | ary[2] # 3 7 | ary[::2] # array([1, 3]) 8 | 9 | 10 | 11 | 12 | np.array([1, 2, 3, 4], dtype=np.float32) 13 | # array([1., 2., 3., 4.], dtype=float32) 14 | 15 | 16 | 17 | arr = np.array([1, 2, 3, 4]) 18 | print(arr.dtype) # dtype('int64') 19 | arr.dtype = np.float32 20 | print(arr) 21 | # [1.e-45 0.e+00 3.e-45 0.e+00 4.e-45 0.e+00 6.e-45 0.e+00] 22 | 23 | 24 | 25 | arr = arr.astype(np.float32) 26 | print(arr) 27 | # [1. 2. 3. 4.] -------------------------------------------------------------------------------- /Chapter 01/solving-equations.py: -------------------------------------------------------------------------------- 1 | from scipy import optimize 2 | from math import exp 3 | 4 | def f(x): 5 | return x*(x - 2)*exp(3 - x) 6 | 7 | def fp(x): 8 | return -(x**2 - 4*x + 2)*exp(3 - x) 9 | 10 | 11 | optimize.newton(f, 1., x1=1.5) # Using x1 = 1.5 and the secant method 12 | # 1.9999999999999862 13 | optimize.newton(f, 1., fprime=fp) # Using Newton-Raphson method 14 | # 2.0 15 | 16 | 17 | -------------------------------------------------------------------------------- /Chapter 01/sparse-matrices.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy import sparse 3 | 4 | T = sparse.diags([-1, 2, -1], (-1, 0, 1), shape=(5, 5), format="csr") 5 | T.toarray() 6 | # array([[ 2, -1, 0, 0, 0], 7 | # [-1, 2, -1, 0, 0], 8 | # [ 0, -1, 2, -1, 0], 9 | # [ 0, 0, -1, 2, -1], 10 | # [ 0, 0, 0, -1, 2]]) 11 | 12 | from scipy.sparse import linalg 13 | linalg.spsolve(T.tocsr(), np.array([1, 2, 3, 4, 5])) 14 | # array([ 5.83333333, 10.66666667, 13.5, 13.33333333, 9.16666667]) 15 | -------------------------------------------------------------------------------- /Chapter 01/systems-of-equations.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from numpy import linalg 3 | 4 | A = np.array([[3, -2, 1], [1, 1, -2], [-3, -2, 1]]) 5 | b = np.array([7, -4, 1]) 6 | 7 | linalg.solve(A, b) # array([ 1., -1., 2.]) 8 | 9 | 10 | -------------------------------------------------------------------------------- /Chapter 01/useful-array-creation-routines.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | np.linspace(0, 1, 5) # array([0., 0.25, 0.5, 0.75, 1.0]) 5 | np.arange(0, 1, 0.3) # array([0.0, 0.3, 0.6, 0.9]) 6 | 7 | -------------------------------------------------------------------------------- /Chapter 02/adding-labels-and-legends-to-plots.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | 4 | y1 = np.array([1.0, 2.0, 3.0, 4.0, 5.0]) 5 | y2 = np.array([1.2, 1.6, 3.1, 4.2, 4.8]) 6 | y3 = np.array([3.2, 1.1, 2.0, 3.9, 2.5]) 7 | 8 | fig, ax = plt.subplots() 9 | 10 | lines = ax.plot(y1, 'o', y2, 'x', y3, '*') 11 | 12 | ax.set_title("Plot of the data y1, y2, and y3") 13 | ax.set_xlabel("x axis label") 14 | ax.set_ylabel("y axis label") 15 | 16 | 17 | 18 | ax.legend(("data y1", "data y2", "data y3")) 19 | 20 | 21 | plt.show() 22 | 23 | 24 | -------------------------------------------------------------------------------- /Chapter 02/adding-subplots.py: -------------------------------------------------------------------------------- 1 | 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | from math import fabs 5 | 6 | def generate_newton_iters(x0, number): 7 | yield x0, fabs(x0 - 1.) 8 | for _ in range(number): 9 | x0 = x0 - (x0*x0 - 1.)/(2*x0) 10 | yield x0, fabs(x0 - 1.) 11 | 12 | 13 | data = np.array(list(generate_newton_iters(2.0, 5))) 14 | iterates, errors = data[:, 0], data[:, 1] 15 | 16 | fig, (ax1, ax2) = plt.subplots(1, 2, tight_layout=True) # 1 row, 2 columns 17 | 18 | ax1.plot(iterates, "x") 19 | ax1.set_title("Iterates") 20 | ax1.set_xlabel("$i$", usetex=True) 21 | ax1.set_ylabel("$x_i$", usetex=True) 22 | 23 | ax2.semilogy(errors, "x") # plot y on a logarithmic scale 24 | ax2.set_title("Error") 25 | ax2.set_xlabel("$i$", usetex=True) 26 | ax2.set_ylabel("Error") 27 | 28 | plt.show() 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /Chapter 02/basic-plotting-with-matplotlib.py: -------------------------------------------------------------------------------- 1 | 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | 5 | def f(x): 6 | return x*(x-2)*np.exp(3 - x) 7 | 8 | 9 | x = np.linspace(-0.5, 3.0) # 100 values between -0.5 and 3.0 10 | y = f(x) 11 | 12 | ax = plt.plot(x, y) 13 | plt.show() 14 | 15 | -------------------------------------------------------------------------------- /Chapter 02/changing-the-plotting-style.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | 6 | 7 | y1 = np.array([1.0, 2.0, 3.0, 4.0, 5.0]) 8 | y2 = np.array([1.2, 1.6, 3.1, 4.2, 4.8]) 9 | y3 = np.array([3.2, 1.1, 2.0, 3.9, 2.5]) 10 | 11 | fig, ax = plt.subplots() 12 | 13 | lines = ax.plot(y1, 'o', y2, 'x', y3, '*') 14 | 15 | 16 | plt.show() 17 | -------------------------------------------------------------------------------- /Chapter 02/customising-3d-plots.py: -------------------------------------------------------------------------------- 1 | 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | from mpl_toolkits import mplot3d 5 | 6 | X = np.linspace(-2, 2) 7 | Y = np.linspace(-2, 2) 8 | x, y = np.meshgrid(X, Y) 9 | t = x**2 + y**2 # small efficiency 10 | z = np.cos(2*np.pi*t)*np.exp(-t) 11 | 12 | 13 | fig = plt.figure() 14 | ax = fig.add_subplot(projection="3d") 15 | ax.plot_surface(x, y, z, cmap="binary_r") 16 | ax.set_title("Surface with colormap") 17 | ax.set_xlabel("$x$") 18 | ax.set_ylabel("$y$") 19 | ax.set_zlabel("$z$") 20 | 21 | 22 | plt.show() 23 | 24 | fig, ax = plt.subplots() 25 | ax.contour(x, y, z, cmap="binary_r") 26 | 27 | ax.set_xlabel("$x$") 28 | ax.set_ylabel("$y$") 29 | ax.set_title("Contour plot with colormap set") 30 | 31 | plt.show() 32 | -------------------------------------------------------------------------------- /Chapter 02/saving-matplotlib-figures.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | 4 | x = np.arange(1, 5, 0.1) 5 | y = x*x 6 | 7 | fig, ax = plt.subplots() 8 | ax.plot(x, y) 9 | ax.set_title("Graph of $y=x^2$", usetex=True) 10 | ax.set_xlabel("$x$", usetex=True) 11 | ax.set_ylabel("$y$", usetex=True) 12 | 13 | fig.savefig("savingfigs.png", dpi=300) 14 | -------------------------------------------------------------------------------- /Chapter 02/surface-and-contour-plots.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | 4 | X = np.linspace(-2, 2) 5 | Y = np.linspace(-1, 1) 6 | 7 | 8 | x, y = np.meshgrid(X, Y) 9 | 10 | z = x**2 * y**3 11 | 12 | 13 | from mpl_toolkits import mplot3d 14 | 15 | fig = plt.figure() 16 | ax = fig.add_subplot(projection="3d") 17 | 18 | ax.plot_surface(x, y, z) 19 | 20 | ax.set_xlabel("$x$") 21 | ax.set_ylabel("$y$") 22 | ax.set_zlabel("$z$") 23 | 24 | ax.set_title("Graph of the function $f(x) = x^2y^3$") 25 | 26 | 27 | plt.show() # paused here 28 | 29 | 30 | fig, ax = plt.subplots() 31 | ax.contour(x, y, z) 32 | ax.set_title("Contours of $f(x) = x^2y^3$") 33 | ax.set_xlabel("$x$") 34 | ax.set_ylabel("$y$") 35 | 36 | 37 | plt.show() 38 | -------------------------------------------------------------------------------- /Chapter 03/discrete-fourier-transforms-for-signals-processing.py: -------------------------------------------------------------------------------- 1 | 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | 5 | 6 | 7 | 8 | def signal(t, freq_1=4.0, freq_2=7.0): 9 | return np.sin(freq_1*2*np.pi*t) + np.sin(freq_2*2*np.pi*t) 10 | 11 | 12 | state = np.random.RandomState(12345) 13 | sample_size = 2**7 14 | sample_t = np.linspace(0, 4, sample_size, dtype=np.float64) 15 | sample_y = signal(sample_t) + state.standard_normal(sample_size) 16 | sample_d = 4. / (sample_size - 1) # Spacing for linspace array 17 | true_signal = signal(sample_t) 18 | 19 | from numpy import fft 20 | 21 | fig1, ax1 = plt.subplots() 22 | ax1.plot(sample_t, sample_y, "k.", label="Noisy signal") 23 | ax1.plot(sample_t, true_signal, "k--", label="True signal") 24 | 25 | ax1.set_title("Sample signal with noise") 26 | ax1.set_xlabel("Time") 27 | ax1.set_ylabel("Amplitude") 28 | ax1.legend() 29 | 30 | 31 | spectrum = fft.fft(sample_y) 32 | 33 | freq = fft.fftfreq(sample_size, sample_d) 34 | pos_freq_i = np.arange(1, sample_size//2, dtype=int) 35 | 36 | psd = np.abs(spectrum[pos_freq_i])**2 + np.abs(spectrum[-pos_freq_i])**2 37 | 38 | fig2, ax2 = plt.subplots() 39 | ax2.plot(freq[pos_freq_i], psd) 40 | ax2.set_title("PSD of the noisy signal") 41 | ax2.set_xlabel("Frequency") 42 | ax2.set_ylabel("Density") 43 | 44 | filtered = pos_freq_i[psd > 1e4] 45 | 46 | new_spec = np.zeros_like(spectrum) 47 | new_spec[filtered] = spectrum[filtered] 48 | new_spec[-filtered] = spectrum[-filtered] 49 | 50 | new_sample = np.real(fft.ifft(new_spec)) 51 | 52 | fig3, ax3 = plt.subplots() 53 | ax3.plot(sample_t, true_signal, color="#8c8c8c", linewidth=1.5, label="True signal") 54 | ax3.plot(sample_t, new_sample, "k--", label="Filtered signal") 55 | ax3.legend() 56 | ax3.set_title("Plot comparing filtered signal and true signal") 57 | ax3.set_xlabel("Time") 58 | ax3.set_ylabel("Amplitude") 59 | 60 | 61 | plt.show() 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /Chapter 03/numerical-integration.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | import numpy as np 4 | 5 | def erf_integrand(t): 6 | return np.exp(-t**2) 7 | 8 | from scipy import integrate 9 | 10 | val_quad, err_quad = integrate.quad(erf_integrand, -1.0, 1.0) 11 | # (1.493648265624854, 1.6582826951881447e-14) 12 | 13 | 14 | val_quadr, err_quadr = integrate.quadrature(erf_integrand, -1.0, 1.0) 15 | # (1.4936482656450039, 7.459897144457273e-10) 16 | -------------------------------------------------------------------------------- /Chapter 03/partial-differential-equations.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | from mpl_toolkits import mplot3d 4 | 5 | alpha = 1 6 | x0 = 0 # Left hand x limit 7 | xL = 2 # Right hand x limit 8 | 9 | N = 10 10 | x = np.linspace(x0, xL, N+1) 11 | h = (xL - x0) / N 12 | 13 | k = 0.01 14 | steps = 100 15 | t = np.array([i*k for i in range(steps+1)]) 16 | 17 | r = alpha*k / h**2 18 | assert r < 0.5, f"Must have r < 0.5, currently r={r}" 19 | 20 | from scipy import sparse 21 | diag = [1, *(1-2*r for _ in range(N-1)), 1] 22 | abv_diag = [0, *(r for _ in range(N-1))] 23 | blw_diag = [*(r for _ in range(N-1)), 0] 24 | 25 | A = sparse.diags([blw_diag, diag, abv_diag], (-1, 0, 1), shape=(N+1, N+1), dtype=np.float64, format="csr") 26 | 27 | u = np.zeros((steps+1, N+1), dtype=np.float64) 28 | 29 | def initial_profile(x): 30 | return 3*np.sin(np.pi*x/2) 31 | 32 | u[0, :] = initial_profile(x) 33 | 34 | for i in range(steps): 35 | u[i+1, :] = A @ u[i, :] 36 | 37 | 38 | X, T = np.meshgrid(x, t) 39 | fig = plt.figure() 40 | ax = fig.add_subplot(projection="3d") 41 | 42 | ax.plot_surface(T, X, u, cmap="hot") 43 | ax.set_title("Solution of the heat equation") 44 | ax.set_xlabel("t") 45 | ax.set_ylabel("x") 46 | ax.set_zlabel("u") 47 | 48 | plt.show() 49 | 50 | -------------------------------------------------------------------------------- /Chapter 03/polynomials-and-calculus.py: -------------------------------------------------------------------------------- 1 | 2 | class Polynomial: 3 | """Basic polynomial class""" 4 | 5 | def __init__(self, coeffs): 6 | self.coeffs = coeffs 7 | 8 | def __repr__(self): 9 | return f"Polynomial({repr(self.coeffs)})" 10 | 11 | def __call__(self, x): 12 | return sum(coeff*x**i for i, coeff in enumerate(self.coeffs)) 13 | 14 | def differentiate(self): 15 | """Differentiate the polynomial and return the derivative""" 16 | coeffs = [i*c for i, c in enumerate(self.coeffs[1:], start=1)] 17 | return Polynomial(coeffs) 18 | 19 | def integrate(self, constant=0): 20 | """Integrate the polynomial and return the integral""" 21 | coeffs = [float(constant)] 22 | coeffs += [c/i for i, c in enumerate(self.coeffs, start=1)] 23 | return Polynomial(coeffs) 24 | 25 | 26 | p = Polynomial([1, -2, 1]) 27 | p.differentiate() 28 | # Polynomial([2, -2]) 29 | p.integrate(constant=1) 30 | # Polynomial([1.0, 1.0, -1.0, 0.333333333333]) 31 | -------------------------------------------------------------------------------- /Chapter 03/solving-equations.py: -------------------------------------------------------------------------------- 1 | from scipy import optimize 2 | from math import exp 3 | 4 | def f(x): 5 | return x*(x - 2)*exp(3 - x) 6 | 7 | def fp(x): 8 | return -(x**2 - 4*x + 2)*exp(3 - x) 9 | 10 | 11 | optimize.newton(f, 1., x1=1.5) # Using x1 = 1.5 and the secant method 12 | # 1.9999999999999862 13 | optimize.newton(f, 1., fprime=fp) # Using Newton-Raphson method 14 | # 2.0 15 | 16 | 17 | -------------------------------------------------------------------------------- /Chapter 03/solving-simple-differential-equations-numerically.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy import integrate 3 | import matplotlib.pyplot as plt 4 | 5 | def f(t, y): 6 | return -0.2*y 7 | 8 | t_range = (0, 5) 9 | 10 | T0 = np.array([50.]) 11 | 12 | def true_solution(t): 13 | return 50.*np.exp(-0.2*t) 14 | 15 | sol = integrate.solve_ivp(f, t_range, T0, max_step=0.1) 16 | 17 | t_vals = sol.t 18 | T_vals = sol.y[0, :] 19 | 20 | fig, (ax1, ax2) = plt.subplots(1, 2, tight_layout=True) 21 | 22 | ax1.plot(t_vals, T_vals) 23 | ax1.set_xlabel("$t$") 24 | ax1.set_ylabel("$T$") 25 | ax1.set_title("Solution of the cooling equation") 26 | 27 | 28 | err = np.abs(T_vals - true_solution(t_vals)) 29 | ax2.semilogy(t_vals, err) 30 | ax2.set_xlabel("$t$") 31 | ax2.set_ylabel("Error") 32 | ax2.set_title("Error in approximation") 33 | 34 | plt.show() 35 | -------------------------------------------------------------------------------- /Chapter 03/solving-systems-of-differential-equations.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | 6 | 7 | def predator_prey_system(t, y): 8 | return np.array([5*y[0] - 0.1*y[0]*y[1], 0.1*y[1]*y[0] - 6*y[1]]) 9 | 10 | 11 | p = np.linspace(0, 100, 25) 12 | w = np.linspace(0, 100, 25) 13 | P, W = np.meshgrid(p, w) 14 | 15 | dp, dw = predator_prey_system(0, np.array([P, W])) 16 | 17 | fig, ax = plt.subplots() 18 | ax.quiver(P, W, dp, dw) 19 | ax.set_title("Population dynamics for two competing species") 20 | ax.set_xlabel("P") 21 | ax.set_ylabel("W") 22 | 23 | 24 | initial_conditions = np.array([85, 40]) 25 | 26 | from scipy import integrate 27 | sol = integrate.solve_ivp(predator_prey_system, (0., 5.), initial_conditions, max_step=0.01) 28 | 29 | 30 | ax.plot(initial_conditions[0], initial_conditions[1], "ko") 31 | ax.plot(sol.y[0, :], sol.y[1, :], "k", linewidth=0.5) 32 | 33 | plt.show() 34 | 35 | -------------------------------------------------------------------------------- /Chapter 03/symbolic-calculus-using-sympy.py: -------------------------------------------------------------------------------- 1 | import sympy 2 | 3 | x = sympy.symbols('x') 4 | 5 | f = (x**2 - 2*x)*sympy.exp(3 - x) 6 | 7 | fp = sympy.simplify(sympy.diff(f)) # (x*(2 - x) + 2*x - 2)*exp(3 - x) 8 | 9 | fp2 = -(x**2 - 4*x + 2)*sympy.exp(3 - x) 10 | 11 | sympy.simplify(fp2 - fp) == 0 # True 12 | 13 | 14 | F = sympy.integrate(f, x) # -x**2*exp(3 - x) 15 | -------------------------------------------------------------------------------- /Chapter 04/analysing-conversion-rates-with-bayesian-techniques.py: -------------------------------------------------------------------------------- 1 | 2 | import numpy as np 3 | import scipy as sp 4 | 5 | import matplotlib.pyplot as plt 6 | 7 | 8 | from scipy.stats import beta as beta_dist 9 | beta_pdf = beta_dist.pdf 10 | 11 | 12 | 13 | prior_alpha = 25 14 | prior_beta = 75 15 | 16 | args = (prior_alpha, prior_beta) 17 | prior_over_33, err = sp.integrate.quad(beta_pdf, 0.33, 1, args=args) 18 | print("Prior probability", prior_over_33) 19 | # 0.037830787030165056 20 | 21 | 22 | observed_successes = 122 23 | observed_failures = 257 24 | 25 | posterior_alpha = prior_alpha + observed_successes 26 | posterior_beta = prior_beta + observed_failures 27 | 28 | args = (posterior_alpha, posterior_beta) 29 | posterior_over_33, err2 = sp.integrate.quad(beta_pdf, 0.33, 1, args=args) 30 | print("Posterior probability", posterior_over_33) 31 | # 0.13686193416281017 32 | 33 | 34 | p = np.linspace(0, 1, 500) 35 | prior_dist = beta_pdf(p, prior_alpha, prior_beta) 36 | posterior_dist = beta_pdf(p, posterior_alpha, posterior_beta) 37 | 38 | fig, ax = plt.subplots() 39 | ax.plot(p, prior_dist, "k--", label="Prior") 40 | ax.plot(p, posterior_dist, "k", label="Posterior") 41 | ax.legend() 42 | ax.set_xlabel("Success rate") 43 | ax.set_ylabel("Density") 44 | ax.set_title("Prior and posterior distributions for success rate") 45 | 46 | 47 | plt.show() 48 | -------------------------------------------------------------------------------- /Chapter 04/changing-the-random-number-generator.py: -------------------------------------------------------------------------------- 1 | 2 | from numpy import random 3 | 4 | 5 | seed_seq = random.SeedSequence() 6 | 7 | print(seed_seq.entropy) 8 | # 9219863422733683567749127389169034574 9 | 10 | 11 | bit_gen = random.MT19937(seed_seq) 12 | 13 | rng = random.Generator(bit_gen) 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Chapter 04/creating-random-samples-of-data.py: -------------------------------------------------------------------------------- 1 | from numpy.random import default_rng 2 | 3 | 4 | rng = default_rng(12345) 5 | 6 | sample = rng.choice(30, size=5, replace=False) 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Chapter 04/estimating-parameters-with-monte-carlo-simulations.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | 4 | from numpy.random import default_rng 5 | rng = default_rng(12345) 6 | 7 | 8 | def underlying(x, params): 9 | return params[0]*x**2 + params[1]*x + params[2] 10 | 11 | 12 | size = 100 13 | true_params = [2, -7, 6] 14 | 15 | x_vals = np.linspace(-5, 5, size) 16 | raw_model = underlying(x_vals, true_params) 17 | noise = rng.normal(loc=0.0, scale=10.0, size=size) 18 | sample = raw_model + noise 19 | 20 | fig1, ax1 = plt.subplots() 21 | ax1.scatter(x_vals, sample, label="Sampled data") 22 | ax1.plot(x_vals, raw_model, "k--", label="Underlying model") 23 | ax1.set_title("Sampled data") 24 | ax1.set_xlabel("x") 25 | ax1.set_ylabel("y") 26 | 27 | plt.show() 28 | 29 | 30 | import pymc3 as pm 31 | 32 | 33 | with pm.Model() as model: 34 | params = pm.Normal("params", mu=1, sigma=1, shape=3) 35 | y = underlying(x_vals, params) 36 | y_obs = pm.Normal("y_obs", mu=y, sigma=2, observed=sample) 37 | trace = pm.sample(cores=4) 38 | 39 | 40 | 41 | 42 | fig2, axs2 = plt.subplots(1, 3, tight_layout=True) 43 | 44 | pm.plot_posterior(trace, ax=axs2) 45 | 46 | plt.show() 47 | 48 | estimated_params = trace["params"].mean(axis=0) 49 | print("Estimated parameters", estimated_params) 50 | 51 | estimated = underlying(x_vals, estimated_params) 52 | 53 | fig3, ax3 = plt.subplots() 54 | ax3.plot(x_vals, raw_model, "k", label="True model") 55 | ax3.plot(x_vals, estimated, "k--", label="Estimated model") 56 | ax3.set_title("Plot of true and estimated models") 57 | ax3.set_xlabel("x") 58 | ax3.set_ylabel("y") 59 | ax3.legend() 60 | 61 | 62 | 63 | plt.show() 64 | 65 | 66 | #plt.show() 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /Chapter 04/generating-normally-distributed-random-numbers.py: -------------------------------------------------------------------------------- 1 | 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | 5 | from numpy.random import default_rng 6 | rng = default_rng(12345) 7 | 8 | 9 | mu = 5.0 # mean value 10 | sigma = 3.0 # standard deviation 11 | rands = rng.normal(loc=mu, scale=sigma, size=10000) 12 | 13 | 14 | fig, ax = plt.subplots() 15 | ax.hist(rands, bins=20) 16 | ax.set_title("Histogram of normally distributed data") 17 | ax.set_xlabel("Value") 18 | ax.set_ylabel("Density") 19 | 20 | def normal_dist_curve(x): 21 | return 10000*np.exp(-0.5*((x-mu)/sigma)**2)/(sigma*np.sqrt(2*np.pi)) 22 | 23 | x_range = np.linspace(-5, 15) 24 | y = normal_dist_curve(x_range) 25 | ax.plot(x_range, y, "k--") 26 | 27 | 28 | 29 | plt.show() 30 | -------------------------------------------------------------------------------- /Chapter 04/generating-random-data.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | 4 | from numpy.random import default_rng 5 | rng = default_rng(12345) # changing seed for reproducibility 6 | 7 | random_floats = rng.random(size=(5, 5)) 8 | # array([[0.22733602, 0.31675834, 0.79736546, 0.67625467, 0.39110955], 9 | # [0.33281393, 0.59830875, 0.18673419, 0.67275604, 0.94180287], 10 | # [0.24824571, 0.94888115, 0.66723745, 0.09589794, 0.44183967], 11 | # [0.88647992, 0.6974535 , 0.32647286, 0.73392816, 0.22013496], 12 | # [0.08159457, 0.1598956 , 0.34010018, 0.46519315, 0.26642103]]) 13 | 14 | random_ints = rng.integers(1, 20, endpoint=True, size=10) 15 | # array([12, 17, 10, 4, 1, 3, 2, 2, 3, 12]) 16 | 17 | 18 | dist = rng.random(size=1000) 19 | 20 | 21 | fig, ax = plt.subplots() 22 | ax.hist(dist) 23 | ax.set_title("Histogram of random numbers") 24 | ax.set_xlabel("Value") 25 | ax.set_ylabel("Density") 26 | 27 | 28 | plt.show() 29 | 30 | 31 | -------------------------------------------------------------------------------- /Chapter 04/selecting-items-at-random.py: -------------------------------------------------------------------------------- 1 | 2 | import numpy as np 3 | 4 | rng = np.random.default_rng(12345) 5 | 6 | data = np.arange(15) 7 | probabilities = np.array([0.3, 0.2, 0.1, 0.05, 0.05, 0.05, 0.05, 0.025, 8 | 0.025, 0.025, 0.025, 0.025, 0.025, 0.025, 0.025]) 9 | 10 | assert round(sum(probabilities), 10) == 1.0, "Probabilities must sum to 1" 11 | 12 | selected = rng.choice(data, p=probabilities, replace=True) 13 | # 0 14 | 15 | selected_array = rng.choice(data, p=probabilities, replace=True, size=(5, 5)) 16 | #array([[ 1, 6, 4, 1, 1], 17 | # [ 2, 0, 4, 12, 0], 18 | # [12, 4, 0, 1, 10], 19 | # [ 4, 1, 5, 0, 0], 20 | # [ 0, 1, 1, 0, 7]]) 21 | 22 | -------------------------------------------------------------------------------- /Chapter 04/working-with-random-processes.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import numpy as np 3 | 4 | from numpy.random import default_rng 5 | rng = default_rng(12345) 6 | 7 | rate = 4.0 8 | inter_arrival_times = rng.exponential(scale=1./rate, size=50) 9 | 10 | arrivals = np.add.accumulate(inter_arrival_times) 11 | count = np.arange(50) 12 | 13 | 14 | 15 | 16 | fig1, ax1 = plt.subplots() 17 | 18 | ax1.step(arrivals, count, where="post") 19 | ax1.set_xlabel("Time") 20 | ax1.set_ylabel("Number of arrivals") 21 | ax1.set_title("Arrivals over time") 22 | 23 | 24 | 25 | 26 | from scipy.special import factorial 27 | N = np.arange(15) 28 | def probability(events, time=1, param=rate): 29 | return ((param*time)**events/factorial(events))*np.exp(-param*time) 30 | 31 | 32 | 33 | fig2, ax2 = plt.subplots() 34 | ax2.plot(N, probability(N), "k", label="True distribution") 35 | ax2.set_xlabel("Number of arrivals in 1 time unit") 36 | ax2.set_ylabel("Probability") 37 | ax2.set_title("Probability distribution") 38 | 39 | 40 | estimated_scale = np.mean(inter_arrival_times) 41 | estimated_rate = 1.0/estimated_scale 42 | 43 | 44 | ax2.plot(N, probability(N, param=estimated_rate), "k--", label="Estimated distribution") 45 | ax2.legend() 46 | 47 | 48 | plt.show() 49 | -------------------------------------------------------------------------------- /Chapter 05/coloring-a-network.py: -------------------------------------------------------------------------------- 1 | 2 | import networkx as nx 3 | import matplotlib.pyplot as plt 4 | 5 | G = nx.complete_graph(3) 6 | G.add_nodes_from(range(3, 7)) 7 | G.add_edges_from([ 8 | (2, 3), (2, 4), (2, 6), (0, 3), (0, 6), (1, 6), 9 | (1, 5), (2, 5), (4, 5) 10 | ]) 11 | 12 | 13 | fig, ax = plt.subplots() 14 | nx.draw_circular(G, ax=ax, with_labels=True) 15 | ax.set_title("Scheduling network") 16 | 17 | plt.show() 18 | 19 | 20 | 21 | coloring = nx.greedy_color(G) 22 | print("Coloring", coloring) 23 | # Coloring {2: 0, 0: 1, 1: 2, 5: 1, 6: 3, 3: 2, 4: 2} 24 | 25 | different_colors = set(coloring.values()) 26 | print("Different colors", different_colors) 27 | # Different colors {0, 1, 2, 3} 28 | -------------------------------------------------------------------------------- /Chapter 05/creating-directed-and-weighted-networks.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import networkx as nx 3 | import matplotlib.pyplot as plt 4 | 5 | G = nx.DiGraph() 6 | 7 | G.add_nodes_from(range(5)) 8 | 9 | G.add_edge(0, 1, weight=1.0) 10 | G.add_weighted_edges_from([ 11 | (1, 2, 0.5), (1, 3, 2.0), (2, 3, 0.3), (3, 2, 0.3), 12 | (2, 4, 1.2), (3, 4, 0.8) 13 | ]) 14 | 15 | 16 | fig, ax = plt.subplots() 17 | pos = {0: (-1, 0), 1: (0, 0), 2: (1, 1), 3: (1, -1), 4: (2, 0)} 18 | nx.draw(G, ax=ax, pos=pos, with_labels=True) 19 | ax.set_title("Weighted, directed network") 20 | 21 | plt.show() 22 | 23 | 24 | adj_mat = nx.adjacency_matrix(G).todense() 25 | print(adj_mat) 26 | # [[0. 1. 0. 0. 0. ] 27 | # [0. 0. 0.5 2. 0. ] 28 | # [0. 0. 0. 0.3 1.2] 29 | # [0. 0. 0.3 0. 0.8] 30 | # [0. 0. 0. 0. 0. ]] 31 | -------------------------------------------------------------------------------- /Chapter 05/creating-networks-in-python.py: -------------------------------------------------------------------------------- 1 | import networkx as nx 2 | 3 | 4 | G = nx.Graph() 5 | 6 | G.add_node(1) 7 | G.add_node(2) 8 | 9 | G.add_nodes_from([3, 4, 5, 6]) 10 | 11 | G.add_edge(1, 2) 12 | G.add_edges_from([(2, 3), (3, 4), (3, 5), (3, 6), (4, 5), (5, 6)]) 13 | 14 | 15 | 16 | print(G.nodes) 17 | print(G.edges) 18 | -------------------------------------------------------------------------------- /Chapter 05/finding-minimum-spanning-trees-and-dominating-sets.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | import networkx as nx 4 | import matplotlib.pyplot as plt 5 | 6 | 7 | G = nx.gnm_random_graph(15, 22, seed=12345) 8 | 9 | 10 | fig, ax = plt.subplots() 11 | pos = nx.circular_layout(G) 12 | nx.draw(G, pos=pos, ax=ax, with_labels=True) 13 | ax.set_title("Network with minimum spanning tree overlaid") 14 | 15 | min_span_tree = nx.minimum_spanning_tree(G) 16 | print(list(min_span_tree.edges)) 17 | # [(0, 13), (0, 7), (0, 5), (1, 13), (1, 11), 18 | # (2, 5), (2, 9), (2, 8), (2, 3), (2, 12), 19 | # (3, 4), (4, 6), (5, 14), (8, 10)] 20 | 21 | nx.draw_networkx_edges(min_span_tree, pos=pos, ax=ax, width=1.5, edge_color="r") 22 | 23 | 24 | dominating_set = nx.dominating_set(G) 25 | print("Dominating set", dominating_set) 26 | # Dominating set {0, 1, 2, 4, 10, 14} 27 | 28 | plt.show() 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Chapter 05/finding-shortest-paths.py: -------------------------------------------------------------------------------- 1 | 2 | import networkx as nx 3 | import matplotlib.pyplot as plt 4 | 5 | from numpy.random import default_rng 6 | rng = default_rng(12345) 7 | 8 | G = nx.gnm_random_graph(10, 17, seed=12345) 9 | 10 | fig, ax = plt.subplots() 11 | nx.draw_circular(G, ax=ax, with_labels=True) 12 | ax.set_title("Random network for shortest path finding") 13 | 14 | plt.show() 15 | 16 | for u, v in G.edges: 17 | G.edges[u, v]["weight"] = rng.integers(5, 15) 18 | 19 | 20 | path = nx.shortest_path(G, 7, 9, weight="weight") 21 | print(path) 22 | # [7, 5, 2, 9] 23 | 24 | length = nx.shortest_path_length(G, 7, 9, weight="weight") 25 | print("Length", length) 26 | # Length 32 27 | 28 | -------------------------------------------------------------------------------- /Chapter 05/generating-the-adjacency-matrix-for-a-network.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import networkx as nx 3 | 4 | G = nx.dense_gnm_random_graph(5, 5, seed=12345) 5 | 6 | matrix = nx.adjacency_matrix(G).todense() 7 | print(matrix) 8 | # [[0 0 1 0 0] 9 | # [0 0 1 1 0] 10 | # [1 1 0 0 1] 11 | # [0 1 0 0 1] 12 | # [0 0 1 1 0]] 13 | 14 | paths_len_4 = np.linalg.matrix_power(matrix, 4) 15 | print(paths_len_4) 16 | # [[ 3 5 0 0 5] 17 | # [ 5 9 0 0 9] 18 | # [ 0 0 13 10 0] 19 | # [ 0 0 10 8 0] 20 | # [ 5 9 0 0 9]] 21 | -------------------------------------------------------------------------------- /Chapter 05/getting-the-basic-characteristics-of-networks.py: -------------------------------------------------------------------------------- 1 | 2 | import networkx as nx 3 | import matplotlib.pyplot as plt 4 | 5 | G = nx.Graph() 6 | G.add_nodes_from(range(10)) 7 | G.add_edges_from([ 8 | (0, 1), (1, 2), (2, 3), (2, 4), 9 | (2, 5), (3, 4), (4, 5), (6, 7), 10 | (6, 8), (6, 9), (7, 8), (8, 9) 11 | ]) 12 | 13 | fig, ax = plt.subplots() 14 | nx.draw_circular(G, ax=ax, with_labels=True) 15 | ax.set_title("Simple network") 16 | 17 | plt.show() 18 | 19 | print(nx.info(G)) 20 | # Name: 21 | # Type: Graph 22 | # Number of nodes: 10 23 | # Number of edges: 12 24 | # Average degree: 2.4000 25 | 26 | for i in [0, 2, 7]: 27 | degree = G.degree[i] 28 | print(f"Degree of {i}: {degree}") 29 | # Degree of 0: 1 30 | # Degree of 2: 4 31 | # Degree of 7: 2 32 | 33 | components = list(nx.connected_components(G)) 34 | print(components) 35 | 36 | 37 | density = nx.density(G) 38 | print("Density", density) 39 | # Density 0.26666666666666666 40 | 41 | is_planar, _ = nx.check_planarity(G) 42 | print("Is planar", is_planar) 43 | # Is planar True 44 | 45 | -------------------------------------------------------------------------------- /Chapter 05/quantifying-clustering-in-a-network.py: -------------------------------------------------------------------------------- 1 | 2 | import networkx as nx 3 | import matplotlib.pyplot as plt 4 | 5 | G = nx.Graph() 6 | complete_part = nx.complete_graph(4) 7 | cycle_part = nx.cycle_graph(range(4, 9)) 8 | G.update(complete_part) 9 | G.update(cycle_part) 10 | G.add_edges_from([(0, 8), (3, 4)]) 11 | 12 | fig, ax = plt.subplots() 13 | nx.draw_circular(G, ax=ax, with_labels=True) 14 | ax.set_title("Network with different clustering behavior") 15 | 16 | plt.show() 17 | 18 | cluster_coeffs = nx.clustering(G) 19 | 20 | for i in [0, 2, 6]: 21 | print(f"Node {i}, clustering {cluster_coeffs[i]}") 22 | # Node 0, clustering 0.5 23 | # Node 2, clustering 1.0 24 | # Node 6, clustering 0 25 | 26 | av_clustering = nx.average_clustering(G) 27 | print(av_clustering) 28 | # 0.3333333333333333 29 | -------------------------------------------------------------------------------- /Chapter 05/visualising-networks.py: -------------------------------------------------------------------------------- 1 | 2 | import networkx as nx 3 | import matplotlib.pyplot as plt 4 | 5 | # Graph from "Creating networks" recipe 6 | G = nx.Graph() 7 | 8 | G.add_nodes_from(range(1, 7)) 9 | G.add_edges_from([ 10 | (1, 2), (2, 3), (3, 4), (3, 5), 11 | (3, 6), (4, 5), (5, 6) 12 | ]) 13 | 14 | fig, ax = plt.subplots() 15 | 16 | layout = nx.shell_layout(G) 17 | 18 | nx.draw(G, ax=ax, pos=layout, with_labels=True) 19 | ax.set_title("Simple network drawing") 20 | 21 | 22 | plt.show() 23 | -------------------------------------------------------------------------------- /Chapter 06/creating-interactive-plots-with-Bokeh.py: -------------------------------------------------------------------------------- 1 | 2 | import pandas as pd 3 | import numpy as np 4 | from bokeh import plotting as bk 5 | import matplotlib.pyplot as plt 6 | 7 | from numpy.random import default_rng 8 | rng = default_rng(12345) 9 | 10 | date_range = pd.date_range("2020-01-01", periods=50) 11 | data = np.add.accumulate(rng.normal(0, 3, size=50)) 12 | series = pd.Series(data, index=date_range) 13 | 14 | 15 | bk.output_file("sample.html") 16 | 17 | fig = bk.figure(title="Time series data", 18 | x_axis_label="date", 19 | x_axis_type="datetime", 20 | y_axis_label="value") 21 | 22 | fig.line(date_range, series) 23 | 24 | bk.show(fig) -------------------------------------------------------------------------------- /Chapter 06/creating-series-and-dataframes.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import numpy as np 3 | 4 | from numpy.random import default_rng 5 | rng = default_rng(12345) 6 | 7 | diff_data = rng.normal(0, 1, size=100) 8 | cumulative = np.add.accumulate(diff_data) 9 | 10 | data_series = pd.Series(diff_data) 11 | print(data_series) 12 | 13 | data_frame = pd.DataFrame({ 14 | "diffs": data_series, 15 | "cumulative": data_series.cumsum() 16 | }) 17 | 18 | print(data_frame) 19 | -------------------------------------------------------------------------------- /Chapter 06/getting-descriptive-statistics-from-dataframes.py: -------------------------------------------------------------------------------- 1 | 2 | import pandas as pd 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | 6 | from numpy.random import default_rng 7 | rng = default_rng(12345) 8 | 9 | 10 | uniform = rng.uniform(1, 5, size=100) 11 | normal = rng.normal(1, 2.5, size=100) 12 | bimodal = np.concatenate([rng.normal(0, 1, size=50), rng.normal(6, 1, size=50)]) 13 | 14 | df = pd.DataFrame({ 15 | "uniform": uniform, 16 | "normal": normal, 17 | "bimodal": bimodal 18 | }) 19 | 20 | fig, (ax1, ax2, ax3) = plt.subplots(1, 3, tight_layout=True) 21 | 22 | df["uniform"].plot(kind="hist", title="Uniform", ax=ax1) 23 | df["normal"].plot(kind="hist", title="Normal", ax=ax2) 24 | df["bimodal"].plot(kind="hist", title="Bimodal", ax=ax3, bins=20) 25 | 26 | 27 | descriptive = df.describe() 28 | descriptive.loc["kurtosis"] = df.kurtosis() 29 | print(descriptive) 30 | 31 | uniform_mean = descriptive.loc["mean", "uniform"] 32 | normal_mean = descriptive.loc["mean", "normal"] 33 | bimodal_mean = descriptive.loc["mean", "bimodal"] 34 | 35 | ax1.vlines(uniform_mean, 0, 20) 36 | ax2.vlines(uniform_mean, 0, 25) 37 | ax3.vlines(uniform_mean, 0, 12) 38 | 39 | plt.show() 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Chapter 06/loading-and-storing-data-from-a-dataframe.py: -------------------------------------------------------------------------------- 1 | 2 | import pandas as pd 3 | import numpy as np 4 | from numpy.random import default_rng 5 | rng = default_rng(12345) 6 | 7 | 8 | diffs = rng.normal(0, 1, size=100) 9 | cumulative = np.add.accumulate(diffs) 10 | 11 | data_frame = pd.DataFrame({ 12 | "diffs": diffs, 13 | "cumulative": cumulative 14 | }) 15 | print(data_frame) 16 | 17 | 18 | data_frame.to_csv("sample.csv", index=False) 19 | 20 | 21 | 22 | df = pd.read_csv("sample.csv", index_col=False) 23 | print(df) 24 | -------------------------------------------------------------------------------- /Chapter 06/manipulating-data-frames.py: -------------------------------------------------------------------------------- 1 | 2 | import pandas as pd 3 | import numpy as np 4 | from numpy.random import default_rng 5 | rng = default_rng(12345) 6 | three = rng.uniform(-0.2, 1.0, size=100) 7 | three[three < 0] = np.nan 8 | 9 | data_frame = pd.DataFrame({ 10 | "one": rng.random(size=100), 11 | "two": np.add.accumulate(rng.normal(0, 1, size=100)), 12 | "three": three 13 | }) 14 | 15 | data_frame["four"] = data_frame["one"] > 0.5 16 | 17 | def transform_function(row): 18 | if row["four"]: 19 | return 0.5*row["two"] 20 | return row["one"]*row["two"] 21 | 22 | data_frame["five"] = data_frame.apply(transform_function, axis=1) 23 | 24 | print(data_frame) 25 | 26 | 27 | df = data_frame.dropna() 28 | 29 | print(df) 30 | 31 | 32 | -------------------------------------------------------------------------------- /Chapter 06/plotting-data-from-a-DataFrame.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | import pandas as pd 4 | import numpy as np 5 | import matplotlib.pyplot as plt 6 | from numpy.random import default_rng 7 | rng = default_rng(12345) 8 | 9 | diffs = rng.standard_normal(size=100) 10 | walk = np.add.accumulate(diffs) 11 | df = pd.DataFrame({ 12 | "diffs": diffs, 13 | "walk": walk 14 | }) 15 | 16 | fig, (ax1, ax2) = plt.subplots(1, 2, tight_layout=True) 17 | 18 | df["walk"].plot(ax=ax1, title="Random walk") 19 | ax1.set_xlabel("Index") 20 | ax1.set_ylabel("Value") 21 | 22 | df["diffs"].plot(kind="hist", ax=ax2, title="Histogram of diffs") 23 | ax2.set_xlabel("Difference") 24 | 25 | 26 | plt.show() 27 | -------------------------------------------------------------------------------- /Chapter 06/testing-hypotheses-for-non-parametric-data.py: -------------------------------------------------------------------------------- 1 | 2 | from scipy import stats 3 | from numpy.random import default_rng 4 | rng = default_rng(12345) 5 | 6 | 7 | sample_A = rng.uniform(2.5, 4.5, size=22) 8 | sample_B = rng.uniform(3.0, 4.4, size=25) 9 | sample_C = rng.uniform(3.0, 4.4, size=30) 10 | 11 | significance = 0.05 12 | 13 | statistic, p_value = stats.kruskal(sample_A, sample_B, sample_C) 14 | print(f"Statistic: {statistic}, p value: {p_value}") 15 | # Statistic: 5.09365664638392, p value: 0.07832970895845669 16 | 17 | if p_value <= significance: 18 | print("Accept H0: all medians equal") 19 | else: 20 | print("There are differences between population medians") 21 | # There are differences between population medians 22 | 23 | _, p_A_B = stats.ranksums(sample_A, sample_B) 24 | _, p_A_C = stats.ranksums(sample_A, sample_C) 25 | _, p_B_C = stats.ranksums(sample_B, sample_C) 26 | 27 | if p_A_B > significance: 28 | print("Significant differences between A and B, p value", p_A_B) 29 | # Significant differences between A and B, p value 0.08808151166219029 30 | 31 | 32 | if p_A_C > significance: 33 | print("Significant differences between A and C, p value", p_A_C) 34 | # Significant differences between A and C, p value 0.4257804790323789 35 | 36 | if p_B_C > significance: 37 | print("Significant differences between B and C, p value", p_B_C) 38 | else: 39 | print("No significant differences between B and C, p value", p_B_C) 40 | # No significant differences between B and C, p value 0.037610047044153536 -------------------------------------------------------------------------------- /Chapter 06/testing-hypotheses-using-ANOVA.py: -------------------------------------------------------------------------------- 1 | 2 | from scipy import stats 3 | from numpy.random import default_rng 4 | rng = default_rng(12345) 5 | 6 | current = rng.normal(4.0, 2.0, size=40) 7 | process_a = rng.normal(6.2, 2.0, size=25) 8 | process_b = rng.normal(4.5, 2.0, size=64) 9 | 10 | significance = 0.05 11 | 12 | 13 | F_stat, p_value = stats.f_oneway(current, process_a, process_b) 14 | 15 | print(f"F stat: {F_stat}, p value: {p_value}") 16 | # F stat: 9.949052026027028, p value: 9.732322721019206e-05 17 | 18 | if p_value <= significance: 19 | print("Reject H0: there is a difference between means") 20 | else: 21 | print("Accept H0: all means equal") 22 | # Reject H0: there is a difference between means 23 | -------------------------------------------------------------------------------- /Chapter 06/testing-hypotheses-using-t-tests.py: -------------------------------------------------------------------------------- 1 | 2 | import pandas as pd 3 | from scipy import stats 4 | 5 | sample = pd.Series([ 6 | 2.4, 2.4, 2.9, 2.6, 1.8, 2.7, 2.6, 2.4, 2.8, 2.4, 2.4, 7 | 2.4, 2.7, 2.7, 2.3, 2.4, 2.4, 3.2, 2.2, 2.5, 2.1, 1.8, 8 | 2.9, 2.5, 2.5, 3.2, 2. , 2.3, 3. , 1.5, 3.1, 2.5, 3.1, 9 | 2.4, 3. , 2.5, 2.7, 2.1, 2.3, 2.2, 2.5, 2.6, 2.5, 2.8, 10 | 2.5, 2.9, 2.1, 2.8, 2.1, 2.3 11 | ]) 12 | 13 | mu0 = 2.0 14 | significance = 0.05 15 | 16 | t_statistic, p_value = stats.ttest_1samp(sample, mu0) 17 | 18 | print(f"t stat: {t_statistic}, p value: {p_value}") 19 | # t stat: 9.752368720068665, p value: 4.596949515944238e-13 20 | 21 | 22 | if p_value <= significance: 23 | print("Reject H0 in favour of H1: mu != 2.0") 24 | else: 25 | print("Accept H0: mu = 2.0") 26 | # Reject H0 in favour of H1: mu != 2.0 27 | -------------------------------------------------------------------------------- /Chapter 06/understanding-a-population-using-sampling.py: -------------------------------------------------------------------------------- 1 | 2 | import pandas as pd 3 | import math 4 | from scipy import stats 5 | 6 | sample_data = pd.Series( 7 | [172.3, 171.3, 164.7, 162.9, 172.5, 176.3, 174.8, 171.9, 8 | 176.8, 167.8, 164.5, 179.7, 157.8, 170.6, 189.9, 185. , 9 | 172.7, 165.5, 174.5, 171.5] 10 | ) 11 | 12 | sample_mean = sample_data.mean() 13 | sample_std = sample_data.std() 14 | 15 | print(f"Mean {sample_mean}, st. dev {sample_std}") 16 | # Mean 172.15, st. dev 7.473778724383846 17 | 18 | N = sample_data.count() 19 | std_err = sample_std/math.sqrt(N) 20 | 21 | cv_95, cv_99 = stats.t.ppf([0.975, 0.995], df=N-1) 22 | 23 | pm_95 = cv_95*std_err 24 | conf_interval_95 = [sample_mean - pm_95, sample_mean + pm_95] 25 | pm_99 = cv_99*std_err 26 | conf_interval_99 = [sample_mean - pm_99, sample_mean + pm_99] 27 | 28 | print("95% confidence", conf_interval_95) 29 | # 95% confidence [168.65216388659374, 175.64783611340627] 30 | print("99% confidence", conf_interval_99) 31 | # 99% confidence [167.36884119608774, 176.93115880391227] -------------------------------------------------------------------------------- /Chapter 07/classifying-using-logarithmic-regression.py: -------------------------------------------------------------------------------- 1 | 2 | import numpy as np 3 | import pandas as pd 4 | import matplotlib.pyplot as plt 5 | from numpy.random import default_rng 6 | rng = default_rng(12345) 7 | from sklearn.linear_model import LogisticRegression 8 | from sklearn.metrics import classification_report, roc_curve 9 | 10 | 11 | df = pd.DataFrame({ 12 | "var1": np.concatenate([rng.normal(3.0, 1.5, size=50), rng.normal(-4.0, 2.0, size=50)]), 13 | "var2": rng.uniform(size=100), 14 | "var3": np.concatenate([rng.normal(-2.0, 2.0, size=50), rng.normal(1.5, 0.8, size=50)]) 15 | }) 16 | 17 | 18 | score = 4.0 + df["var1"] - df["var3"] 19 | Y = score >= 0 20 | 21 | 22 | fig1, ax1 = plt.subplots() 23 | ax1.plot(df.loc[Y, "var1"], df.loc[Y, "var3"], "bo", label="True data") 24 | ax1.plot(df.loc[~Y, "var1"], df.loc[~Y, "var3"], "rx", label="False data") 25 | ax1.legend() 26 | ax1.set_xlabel("var1") 27 | ax1.set_ylabel("var3") 28 | ax1.set_title("Scatter plot of var3 against var1") 29 | 30 | plt.show() 31 | 32 | model = LogisticRegression() 33 | model.fit(df, Y) 34 | 35 | 36 | 37 | test_df = pd.DataFrame({ 38 | "var1": np.concatenate([rng.normal(3.0, 1.5, size=25), rng.normal(-4.0, 2.0, size=25)]), 39 | "var2": rng.uniform(size=50), 40 | "var3": np.concatenate([rng.normal(-2.0, 2.0, size=25), rng.normal(1.5, 0.8, size=25)]) 41 | }) 42 | 43 | test_scores = 4.0 + test_df["var1"] - test_df["var3"] 44 | test_Y = test_scores >= 0 45 | 46 | test_predicts = model.predict(test_df) 47 | 48 | plt.show() 49 | 50 | print(classification_report(test_Y, test_predicts)) 51 | -------------------------------------------------------------------------------- /Chapter 07/forecasting-from-time-series-data-using-arima.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | import numpy as np 4 | import pandas as pd 5 | import matplotlib.pyplot as plt 6 | 7 | import statsmodels.api as sm 8 | 9 | from tsdata import generate_sample_data 10 | 11 | sample_ts, test_ts = generate_sample_data(trend=0.2, undiff=True) 12 | 13 | 14 | ts_fig, ts_ax = plt.subplots(tight_layout=True) 15 | sample_ts.plot(ax=ts_ax, c="b", label="Observed") 16 | ts_ax.set_title("Training time series data") 17 | ts_ax.set_xlabel("Date") 18 | ts_ax.set_ylabel("Value") 19 | 20 | 21 | 22 | diffs = sample_ts.diff().dropna() 23 | 24 | 25 | ap_fig, (acf_ax, pacf_ax) = plt.subplots(2, 1, tight_layout=True, sharex=True) 26 | sm.graphics.tsa.plot_acf(diffs, ax=acf_ax) 27 | sm.graphics.tsa.plot_pacf(diffs, ax=pacf_ax) 28 | acf_ax.set_ylabel("Value") 29 | pacf_ax.set_xlabel("Lag") 30 | pacf_ax.set_ylabel("Value") 31 | 32 | 33 | model = sm.tsa.ARIMA(sample_ts, order=(1,1,1)) 34 | fitted = model.fit(trend="c") 35 | print(fitted.summary()) 36 | 37 | forecast, std_err, fc_ci = fitted.forecast(steps=50) 38 | forecast_dates = pd.date_range("2021-01-01", periods=50) 39 | forecast = pd.Series(forecast, index=forecast_dates) 40 | 41 | 42 | forecast.plot(ax=ts_ax, c="g", label="Forecast") 43 | ts_ax.fill_between(forecast_dates, fc_ci[:, 0], fc_ci[:, 1], 44 | color="r", alpha=0.4) 45 | 46 | 47 | test_ts.plot(ax=ts_ax, c="k", label="Actual") 48 | ts_ax.legend() 49 | 50 | 51 | plt.show() -------------------------------------------------------------------------------- /Chapter 07/forecasting-seasonal-data-with-arima.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | import matplotlib.pyplot as plt 4 | 5 | import statsmodels.api as sm 6 | 7 | from tsdata import generate_sample_data 8 | 9 | sample_ts, test_ts = generate_sample_data(undiff=True, seasonal=True) 10 | 11 | ts_fig, ts_ax = plt.subplots(tight_layout=True) 12 | sample_ts.plot(ax=ts_ax, title="Time series", label="Observed") 13 | ts_ax.set_xlabel("Date") 14 | ts_ax.set_ylabel("Value") 15 | 16 | 17 | 18 | ap_fig, (acf_ax, pacf_ax) = plt.subplots(2, 1, sharex=True, tight_layout=True) 19 | sm.graphics.tsa.plot_acf(sample_ts, ax=acf_ax) 20 | sm.graphics.tsa.plot_pacf(sample_ts, ax=pacf_ax) 21 | pacf_ax.set_xlabel("Lag") 22 | acf_ax.set_ylabel("Value") 23 | pacf_ax.set_ylabel("Value") 24 | 25 | 26 | diffs = sample_ts.diff().dropna() 27 | dap_fig, (dacf_ax, dpacf_ax) = plt.subplots(2, 1, sharex=True, tight_layout=True) 28 | sm.graphics.tsa.plot_acf(diffs, ax=dacf_ax, title="Differenced ACF") 29 | sm.graphics.tsa.plot_pacf(diffs, ax=dpacf_ax, title="Differenced PACF") 30 | dpacf_ax.set_xlabel("Lag") 31 | dacf_ax.set_ylabel("Value") 32 | dpacf_ax.set_ylabel("Value") 33 | 34 | 35 | 36 | model = sm.tsa.SARIMAX(sample_ts, order=(1, 1, 1), seasonal_order=(1, 0, 0, 7)) 37 | fitted_seasonal = model.fit() 38 | print(fitted_seasonal.summary()) 39 | fitted_seasonal.fittedvalues.plot(ax=ts_ax, c="r", label="Predicted") 40 | 41 | forecast_result = fitted_seasonal.get_forecast(steps=50) 42 | forecast_index = pd.date_range("2021-01-01", periods=50) 43 | forecast = forecast_result.predicted_mean 44 | 45 | 46 | forecast.plot(ax=ts_ax, c="g", label="Forecasts") 47 | conf = forecast_result.conf_int() 48 | ts_ax.fill_between(forecast_index, conf["lower y"], conf["upper y"], color="r", alpha=0.4) 49 | test_ts.plot(ax=ts_ax, color="k", label="Actual future") 50 | ts_ax.legend() 51 | 52 | 53 | plt.show() 54 | 55 | -------------------------------------------------------------------------------- /Chapter 07/modelling-time-series-data-with-arma.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import statsmodels.api as sm 3 | 4 | from tsdata import generate_sample_data 5 | 6 | sample_ts, _ = generate_sample_data() 7 | 8 | 9 | ts_fig, ts_ax = plt.subplots(tight_layout=True) 10 | sample_ts.plot(ax=ts_ax, label="Observed") 11 | ts_ax.set_title("Time series data") 12 | ts_ax.set_xlabel("Date") 13 | ts_ax.set_ylabel("Value") 14 | 15 | 16 | 17 | adf_results = sm.tsa.adfuller(sample_ts) 18 | adf_pvalue = adf_results[1] 19 | print("Augmented Dickey-Fuller test:\nP-value:", adf_pvalue) 20 | 21 | 22 | ap_fig, (acf_ax, pacf_ax) = plt.subplots(2, 1, sharex=True, tight_layout=True) 23 | sm.graphics.tsa.plot_acf(sample_ts, ax=acf_ax, title="Observed autocorrelation") 24 | sm.graphics.tsa.plot_pacf(sample_ts, ax=pacf_ax, title="Observed partial autocorrelation") 25 | pacf_ax.set_xlabel("Lags") 26 | pacf_ax.set_ylabel("Value") 27 | acf_ax.set_ylabel("Value") 28 | 29 | 30 | 31 | arma_model = sm.tsa.ARMA(sample_ts, order=(1, 1)) 32 | 33 | arma_results = arma_model.fit() 34 | print(arma_results.summary()) 35 | 36 | residuals = arma_results.resid 37 | rap_fig, (racf_ax, rpacf_ax) = plt.subplots(2, 1, sharex=True, tight_layout=True) 38 | sm.graphics.tsa.plot_acf(residuals, ax=racf_ax, title="Residual autocorrelation") 39 | sm.graphics.tsa.plot_pacf(residuals, ax=rpacf_ax, title="Residual partial autocorrelation") 40 | rpacf_ax.set_xlabel("Lags") 41 | rpacf_ax.set_ylabel("Value") 42 | racf_ax.set_ylabel("Value") 43 | 44 | 45 | 46 | fitted = arma_results.fittedvalues 47 | fitted.plot(c="r", ax=ts_ax, label="Fitted") 48 | ts_ax.legend() 49 | 50 | 51 | 52 | plt.show() 53 | 54 | 55 | -------------------------------------------------------------------------------- /Chapter 07/tsdata.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | import numpy as np 3 | import pandas as pd 4 | import itertools 5 | 6 | from numpy.random import default_rng 7 | 8 | 9 | 10 | def _get_n(iterable, n): 11 | return list(itertools.islice(iterable, n)) 12 | 13 | def generate_ma(*coeffs, std=1.0, seed=12345): 14 | rng = default_rng(seed=seed) 15 | n = len(coeffs) 16 | past_terms = deque(maxlen=n) 17 | past_terms.extend([0.0]*n) 18 | 19 | coeffs = tuple(reversed(coeffs)) 20 | 21 | while True: 22 | err = rng.normal(0, std) 23 | yield err + sum(c*e for c, e in zip(coeffs, past_terms)) 24 | past_terms.append(err) 25 | 26 | def generate_ar(*coeffs, const=0.0, start=0.0): 27 | n = len(coeffs) 28 | past_terms = deque(maxlen=n) 29 | past_terms.extend([0.0]*(n-1)) 30 | past_terms.append(start) 31 | 32 | coeffs = tuple(reversed(coeffs)) 33 | 34 | while True: 35 | curr = const + sum(c*t for c, t in zip(coeffs, past_terms)) 36 | yield curr 37 | past_terms.append(curr) 38 | 39 | 40 | def generate_arma(ar_coeffs=(0.9,), const=0.0, start=0.0, 41 | ma_coeffs=(), noise_std=1.0, seed=None): 42 | n = len(ar_coeffs) 43 | past_terms = deque(maxlen=n) 44 | past_terms.extend([0.0]*(n-1)) 45 | past_terms.append(start) 46 | 47 | coeffs = tuple(reversed(ar_coeffs)) 48 | 49 | yield start 50 | 51 | ma_proc = generate_ma(*ma_coeffs, std=noise_std, seed=seed) 52 | 53 | for err in ma_proc: 54 | curr = const + err + sum(c*t for c, t in zip(coeffs, past_terms)) 55 | yield curr 56 | past_terms.append(curr) 57 | 58 | 59 | def undifference(iterable): 60 | tot = next(iterable) # first term 61 | for cur in iterable: 62 | yield tot 63 | tot += cur 64 | 65 | def add_season_ar(iterable, period=7, coeffs=(0.7,)): 66 | n = len(coeffs) 67 | coeffs = tuple(reversed(coeffs)) 68 | N = n + period - 1 69 | past_vals = deque(maxlen=N) 70 | past_vals.extend([0.0]*N) 71 | 72 | for item in iterable: 73 | new = item + sum(coeffs[i]*past_vals[i] for i in range(n)) 74 | yield new 75 | past_vals.append(new) 76 | 77 | 78 | def generate_sample_data(train=366, test=50, trend=0.0, undiff=False, seasonal=False): 79 | gen = generate_arma(seed=12345, const=trend, ar_coeffs=(0.8,), ma_coeffs=(-0.5,)) 80 | 81 | if seasonal: 82 | gen = add_season_ar(gen) 83 | 84 | if undiff: 85 | gen = undifference(gen) 86 | 87 | indices = pd.date_range("2020-01-01", periods=train+test) 88 | data = _get_n(gen, train+test) 89 | return (pd.Series(data[:-test], index=indices[:-test]), 90 | pd.Series(data[-test:], index=indices[-test:])) 91 | 92 | 93 | 94 | if __name__ == "__main__": 95 | import matplotlib.pyplot as plt 96 | 97 | gen = generate_arma(seed=12345, ar_coeffs=(0.9,), ma_coeffs=(-0.5,)) 98 | vals = _get_n(gen, 500) 99 | 100 | plt.plot(vals) 101 | plt.show() 102 | 103 | 104 | -------------------------------------------------------------------------------- /Chapter 07/using-linear-regression.py: -------------------------------------------------------------------------------- 1 | 2 | import numpy as np 3 | import statsmodels.api as sm 4 | import matplotlib.pyplot as plt 5 | 6 | from numpy.random import default_rng 7 | rng = default_rng(12345) 8 | 9 | x = np.linspace(0, 5, 25) 10 | rng.shuffle(x) 11 | trend = 2.0 12 | shift = 5.0 13 | y1 = trend*x + shift + rng.normal(0, 0.5, size=25) 14 | y2 = trend*x + shift + rng.normal(0, 5, size=25) 15 | 16 | fig, ax = plt.subplots() 17 | ax.scatter(x, y1, c="b", label="Good correlation") 18 | ax.scatter(x, y2, c="r", label="Bad correlation") 19 | ax.legend() 20 | ax.set_xlabel("X"), 21 | ax.set_ylabel("Y") 22 | ax.set_title("Scatter plot of data with best fit lines") 23 | 24 | pred_x = sm.add_constant(x) 25 | 26 | model1 = sm.OLS(y1, pred_x).fit() 27 | print(model1.summary()) 28 | 29 | model2 = sm.OLS(y2, pred_x).fit() 30 | print(model2.summary()) 31 | 32 | model_x = sm.add_constant(np.linspace(0, 5)) 33 | 34 | 35 | model_y1 = model1.predict(model_x) 36 | model_y2 = model2.predict(model_x) 37 | 38 | 39 | ax.plot(model_x[:, 1], model_y1, 'b') 40 | ax.plot(model_x[:, 1], model_y2, 'r') 41 | 42 | 43 | plt.show() 44 | -------------------------------------------------------------------------------- /Chapter 07/using-multilinear-regression.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | import statsmodels.api as sm 4 | import matplotlib.pyplot as plt 5 | 6 | from numpy.random import default_rng 7 | rng = default_rng(12345) 8 | 9 | 10 | p_vars = pd.DataFrame({ 11 | "const": np.ones((100,)), 12 | "X1": rng.uniform(0, 15, size=100), 13 | "X2": rng.uniform(0, 25, size=100), 14 | "X3": rng.uniform(5, 25, size=100) 15 | }) 16 | 17 | residuals = rng.normal(0.0, 12.0, size=100) 18 | Y = -10.0 + 5.0*p_vars["X1"] - 2.0*p_vars["X2"] + residuals 19 | 20 | 21 | fig, (ax1, ax2, ax3) = plt.subplots(1, 3, sharey=True, tight_layout=True) 22 | ax1.scatter(p_vars["X1"], Y) 23 | ax2.scatter(p_vars["X2"], Y) 24 | ax3.scatter(p_vars["X3"], Y) 25 | 26 | ax1.set_title("Y against X1") 27 | ax1.set_xlabel("X1") 28 | ax1.set_ylabel("Y") 29 | ax2.set_title("Y against X2") 30 | ax2.set_xlabel("X2") 31 | ax3.set_title("Y against X3") 32 | ax3.set_xlabel("X3") 33 | 34 | 35 | plt.show() 36 | 37 | model = sm.OLS(Y, p_vars).fit() 38 | print(model.summary()) 39 | 40 | second_model = sm.OLS(Y, p_vars.loc[:, "const":"X2"]).fit() 41 | print(second_model.summary()) -------------------------------------------------------------------------------- /Chapter 07/using-prophet-to-model-time-series.py: -------------------------------------------------------------------------------- 1 | 2 | import pandas as pd 3 | import matplotlib.pyplot as plt 4 | from fbprophet import Prophet 5 | 6 | from tsdata import generate_sample_data 7 | 8 | 9 | sample_ts, test_ts = generate_sample_data(undiff=True, trend=0.2) 10 | 11 | 12 | df_for_prophet = pd.DataFrame({ 13 | "ds": sample_ts.index, # dates 14 | "y": sample_ts.values # values 15 | }) 16 | 17 | model = Prophet() 18 | model.fit(df_for_prophet) 19 | 20 | 21 | forecast_df = model.make_future_dataframe(periods=50) 22 | 23 | 24 | forecast = model.predict(forecast_df) 25 | 26 | 27 | fig, ax = plt.subplots(tight_layout=True) 28 | sample_ts.plot(ax=ax, label="Observed", title="Forecasts") 29 | forecast.plot(x="ds", y="yhat", ax=ax, c="r", label="Predicted") 30 | ax.fill_between(forecast["ds"].values, forecast["yhat_lower"].values, 31 | forecast["yhat_upper"].values, color="r", alpha=0.4) 32 | test_ts.plot(ax=ax, c="k", label="Future") 33 | ax.legend() 34 | ax.set_xlabel("Date") 35 | ax.set_ylabel("Value") 36 | 37 | 38 | plt.show() 39 | -------------------------------------------------------------------------------- /Chapter 08/computing-convex-hulls.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | import matplotlib as mpl 4 | import matplotlib.pyplot as plt 5 | 6 | from numpy.random import default_rng 7 | rng = default_rng(12345) 8 | 9 | from shapely.geometry import MultiPoint 10 | 11 | raw_points = rng.uniform(-1.0, 1.0, size=(50, 2)) 12 | 13 | 14 | fig, ax = plt.subplots() 15 | ax.plot(raw_points[:, 0], raw_points[:, 1], "k.") 16 | ax.set_axis_off() 17 | 18 | 19 | points = MultiPoint(raw_points) 20 | 21 | convex_hull = points.convex_hull 22 | 23 | patch = mpl.patches.Polygon(convex_hull.exterior, alpha=0.5, ec="k", lw=1.2) 24 | 25 | ax.add_patch(patch) 26 | 27 | 28 | plt.show() -------------------------------------------------------------------------------- /Chapter 08/constructing-bezier-curves.py: -------------------------------------------------------------------------------- 1 | from math import comb as binom 2 | import matplotlib.pyplot as plt 3 | import numpy as np 4 | 5 | 6 | class Bezier: 7 | 8 | def __init__(self, *points): 9 | self.points = points 10 | self.nodes = n = len(points) - 1 11 | self.degree = l = points[0].size 12 | 13 | self.coeffs = [binom(n, i)*p.reshape((l, 1)) for i, p in enumerate(points)] 14 | 15 | def __call__(self, t): 16 | n = self.nodes 17 | t = t.reshape((1, t.size)) 18 | vals = [c @ (t**i)*(1-t)**(n-i) for i, c in enumerate(self.coeffs)] 19 | return np.sum(vals, axis=0) 20 | 21 | 22 | p1 = np.array([0.0, 0.0]) 23 | p2 = np.array([0.0, 1.0]) 24 | p3 = np.array([1.0, 1.0]) 25 | p4 = np.array([1.0, 3.0]) 26 | 27 | 28 | fig, ax = plt.subplots() 29 | ax.plot([0.0, 0.0, 1.0, 1.0], [0.0, 1.0, 1.0, 3.0], "*--k") 30 | ax.set(xlabel="x", ylabel="y", title="Bezier curve with 4 nodes, degree 3") 31 | 32 | 33 | b_curve = Bezier(p1, p2, p3, p4) 34 | 35 | 36 | t = np.linspace(0, 1) 37 | v = b_curve(t) 38 | 39 | ax.plot(v[0,:], v[1, :]) 40 | 41 | plt.show() -------------------------------------------------------------------------------- /Chapter 08/finding-edges-in-images.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | from skimage.io import imread 3 | from skimage.feature import canny 4 | 5 | 6 | image = imread("mandelbrot.png", as_gray=True) 7 | 8 | 9 | edges = canny(image, sigma=0.5) 10 | 11 | fig, ax = plt.subplots() 12 | ax.imshow(edges, cmap="gray_r") 13 | ax.set_axis_off() 14 | 15 | 16 | 17 | plt.show() -------------------------------------------------------------------------------- /Chapter 08/finding-interior-points.py: -------------------------------------------------------------------------------- 1 | import matplotlib as mpl 2 | import matplotlib.pyplot as plt 3 | 4 | from shapely.geometry import Polygon, Point 5 | 6 | polygon = Polygon( 7 | [(0, 2), (-1, 1), (-0.5, -1), (0.5, -1), (1, 1)], 8 | ) 9 | 10 | fig, ax = plt.subplots() 11 | poly_patch = mpl.patches.Polygon(polygon.exterior, ec="k", lw="1", alpha=0.5) 12 | ax.add_patch(poly_patch) 13 | ax.set(xlim=(-1.05, 1.05), ylim=(-1.05, 2.05)) 14 | ax.set_axis_off() 15 | 16 | 17 | p1 = Point(0.0, 0.0) 18 | p2 = Point(-1.0, -0.75) 19 | 20 | ax.plot(0.0, 0.0, "k*") 21 | ax.annotate("p1", (0.0, 0.0), (0.05, 0.0)) 22 | ax.plot(-0.8, -0.75, "k*") 23 | ax.annotate("p2", (-0.8, -0.75), (-0.8 + 0.05, -0.75)) 24 | 25 | plt.show() 26 | 27 | print("p1 inside polygon?", polygon.contains(p1)) 28 | print("p2 inside polygon?", polygon.contains(p2)) 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Chapter 08/mandelbrot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Applying-Math-with-Python/6fcebc7714d01e9042c218aa21cc92a9541100fb/Chapter 08/mandelbrot.png -------------------------------------------------------------------------------- /Chapter 08/triangulating-polygonal-regions.py: -------------------------------------------------------------------------------- 1 | import matplotlib as mpl 2 | import matplotlib.pyplot as plt 3 | import numpy as np 4 | 5 | from shapely.geometry import Polygon 6 | from shapely.ops import triangulate 7 | 8 | polygon = Polygon( 9 | [(2.0, 1.0), (2.0, 1.5), (-4.0, 1.5), (-4.0, 0.5), (-3.0, -1.5), 10 | (0.0, -1.5), (1.0, -2.0), (1.0, -0.5), (0.0, -1.0), (-0.5, -1.0), 11 | (-0.5, 1.0)], 12 | holes=[np.array([[-1.5, -0.5], [-1.5, 0.5], [-2.5, 0.5], [-2.5, -0.5]])] 13 | ) 14 | 15 | fig, ax = plt.subplots() 16 | plt_poly = mpl.patches.Polygon(polygon.exterior, ec="k", lw="1", alpha=0.5, zorder=0) 17 | ax.add_patch(plt_poly) 18 | plt_hole = mpl.patches.Polygon(polygon.interiors[0], ec="k", fc="w") 19 | ax.add_patch(plt_hole) 20 | ax.set(xlim=(-4.05, 2.05), ylim=(-2.05, 1.55)) 21 | ax.set_axis_off() 22 | 23 | 24 | triangles = triangulate(polygon) 25 | 26 | filtered = filter(lambda p: polygon.contains(p), triangles) 27 | 28 | patches = map(lambda p: mpl.patches.Polygon(p.exterior), filtered) 29 | col = mpl.collections.PatchCollection(patches, fc="none", ec="k") 30 | 31 | ax.add_collection(col) 32 | 33 | 34 | plt.show() 35 | -------------------------------------------------------------------------------- /Chapter 08/visualizing-two-dimensional-geometric-figures.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import matplotlib.pyplot as plt 3 | from matplotlib.patches import Circle 4 | from matplotlib.collections import PatchCollection 5 | 6 | 7 | data = np.loadtxt("swisscheese-grid-10411.csv") 8 | 9 | 10 | fig, ax = plt.subplots() 11 | 12 | outer = Circle((0.0, 0.0), 1.0, zorder=0, fc="k") 13 | ax.add_patch(outer) 14 | 15 | 16 | col = PatchCollection( 17 | (Circle((x, y), r) for x, y, r in data), 18 | facecolor="white", zorder=1, linewidth=0.2, 19 | ls="-", ec="k" 20 | ) 21 | ax.add_collection(col) 22 | 23 | ax.set_xlim((-1.1, 1.1)) 24 | ax.set_ylim((-1.1, 1.1)) 25 | ax.set_axis_off() 26 | 27 | 28 | plt.show() -------------------------------------------------------------------------------- /Chapter 09/analyzing-simple-two-player-games.py: -------------------------------------------------------------------------------- 1 | 2 | import numpy as np 3 | import nashpy as nash 4 | 5 | 6 | you = np.array([[1, 3], [1, 4]]) 7 | colleague = np.array([[3, 2], [2, 2]]) 8 | dilemma = nash.Game(you, colleague) 9 | 10 | 11 | 12 | print(dilemma[[1, 0], [1, 0]]) # [1 3] 13 | print(dilemma[[1, 0], [0, 1]]) # [3 2] 14 | print(dilemma[[0, 1], [1, 0]]) # [1 2] 15 | print(dilemma[[0, 1], [0, 1]]) # [4 2] 16 | 17 | 18 | print(dilemma[[0.1, 0.9], [0.5, 0.5]]) # [2.45 2.05] -------------------------------------------------------------------------------- /Chapter 09/computing-nash-equilibria.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | import numpy as np 4 | import nashpy as nash 5 | 6 | 7 | rps_p1 = np.array([ 8 | [ 0, -1, 1], # rock payoff 9 | [ 1, 0, -1], # papper payoff 10 | [-1, 1, 0] # scissors payoff 11 | ]) 12 | 13 | rps_p2 = rps_p1.transpose() 14 | 15 | rock_paper_scissors = nash.Game(rps_p1, rps_p2) 16 | 17 | equilibria = rock_paper_scissors.support_enumeration() 18 | 19 | for p1, p2 in equilibria: 20 | print("Player 1", p1) 21 | print("Player 2", p2) -------------------------------------------------------------------------------- /Chapter 09/minimising-a-non-linear-system.py: -------------------------------------------------------------------------------- 1 | 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | from mpl_toolkits.mplot3d import Axes3D 5 | from scipy import optimize 6 | 7 | 8 | 9 | def func(x): 10 | return ((x[0] - 0.5)**2 + (x[1] + 0.5)**2)*np.cos(0.5*x[0]*x[1]) 11 | 12 | x_r = np.linspace(-1, 1) 13 | y_r = np.linspace(-2, 2) 14 | 15 | x, y = np.meshgrid(x_r, y_r) 16 | 17 | z = func([x, y]) 18 | 19 | 20 | fig = plt.figure(tight_layout=True) 21 | ax = fig.add_subplot(projection="3d") 22 | ax.tick_params(axis="both", which="major", labelsize=9) 23 | ax.set(xlabel="x", ylabel="y", zlabel="z") 24 | ax.set_title("Objective function") 25 | 26 | ax.plot_surface(x, y, z, alpha=0.7) 27 | 28 | x0 = np.array([-0.5, 1.0]) 29 | ax.plot([x0[0]], [x0[1]], func(x0), "r*") 30 | 31 | result = optimize.minimize(func, x0, tol=1e-6, method="Nelder-Mead") 32 | print(result) 33 | 34 | 35 | ax.plot([result.x[0]], [result.x[1]], [result.fun], "r*") 36 | 37 | plt.show() -------------------------------------------------------------------------------- /Chapter 09/minimising-simple-linear-systems.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | import numpy as np 4 | from scipy import optimize 5 | import matplotlib.pyplot as plt 6 | from mpl_toolkits.mplot3d import Axes3D 7 | 8 | 9 | A = np.array([ 10 | [2, 1], # 2*x0 + x1 <= 6 11 | [-1, -1] # -x0 - x1 <= -4 12 | ]) 13 | b = np.array([6, -4]) 14 | 15 | 16 | x0_bounds = (-3, 14) # -3 <= x0 <= 14 17 | x1_bounds = (2, 12) # 2 <= x1 <= 12 18 | 19 | c = np.array([1, 5]) 20 | 21 | 22 | def func(x): 23 | return np.tensordot(c, x, axes=1) 24 | 25 | 26 | fig = plt.figure() 27 | ax = fig.add_subplot(projection="3d") 28 | ax.set(xlabel="x0", ylabel="x1", zlabel="func") 29 | ax.set_title("Values in feasible region") 30 | 31 | X0 = np.linspace(*x0_bounds) 32 | X1 = np.linspace(*x1_bounds) 33 | x0, x1 = np.meshgrid(X0, X1) 34 | z = func([x0, x1]) 35 | 36 | ax.plot_surface(x0, x1, z, alpha=0.3) 37 | 38 | 39 | Y = (b[0] - A[0, 0]*X0) / A[0, 1] 40 | I = np.logical_and(Y >= x1_bounds[0], Y <= x1_bounds[1]) 41 | ax.plot(X0[I], Y[I], func([X0[I], Y[I]]), "r", lw=1.5) 42 | 43 | Y = (b[1] - A[1, 0]*X0) / A[1, 1] 44 | I = np.logical_and(Y >= x1_bounds[0], Y <= x1_bounds[1]) 45 | ax.plot(X0[I], Y[I], func([X0[I], Y[I]]), "r", lw=1.5) 46 | 47 | 48 | B = np.tensordot(A, np.array([x0, x1]), axes=1) 49 | II = np.logical_and(B[0, ...] <= b[0], B[1, ...] <= b[1]) 50 | ax.plot_trisurf(x0[II], x1[II], z[II], color="b", alpha=0.5) 51 | 52 | 53 | res = optimize.linprog(c, A_ub=A, b_ub=b, bounds=(x0_bounds, x1_bounds)) 54 | print(res) 55 | 56 | ax.plot([res.x[0]], [res.x[1]], [res.fun], "k*") 57 | 58 | 59 | plt.show() -------------------------------------------------------------------------------- /Chapter 09/using-gradient-descent-methods.py: -------------------------------------------------------------------------------- 1 | 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | from mpl_toolkits.mplot3d import Axes3D 5 | 6 | 7 | 8 | def descend(func, x0, grad, bounds, tol=1e-8, max_iter=100): 9 | xn = x0 10 | xnm1 = np.inf 11 | grad_xn = grad(x0) 12 | 13 | for i in range(max_iter): 14 | 15 | if np.linalg.norm(xn - xnm1) < tol: 16 | break 17 | 18 | direction = -grad_xn 19 | 20 | xnm1 = xn 21 | xn = xn + 0.2*direction 22 | grad_xn = grad(xn) 23 | yield i, xn, func(xn), grad_xn 24 | 25 | 26 | def func(x): 27 | return ((x[0] - 0.5)**2 + (x[1] + 0.5)**2)*np.cos(0.5*x[0]*x[1]) 28 | 29 | 30 | x_r = np.linspace(-1, 1) 31 | y_r = np.linspace(-2, 2) 32 | 33 | x, y = np.meshgrid(x_r, y_r) 34 | 35 | z = func([x, y]) 36 | 37 | 38 | surf_fig = plt.figure(tight_layout=True) 39 | surf_ax = surf_fig.add_subplot(projection="3d") 40 | surf_ax.tick_params(axis="both", which="major", labelsize=9) 41 | surf_ax.set(xlabel="x", ylabel="y", zlabel="z") 42 | surf_ax.set_title("Objective function") 43 | 44 | surf_ax.plot_surface(x, y, z, alpha=0.7) 45 | 46 | 47 | x0 = np.array([-0.8, 1.3]) 48 | surf_ax.plot([x0[0]], [x0[1]], func(x0), "r*") 49 | 50 | 51 | def grad(x): 52 | c1 = x[0]**2 - x[0] + x[1]**2 + x[1] + 0.5 53 | cos_t = np.cos(0.5*x[0]*x[1]) 54 | sin_t = np.sin(0.5*x[0]*x[1]) 55 | return np.array([ 56 | (2*x[0]-1)*cos_t - 0.5*x[1]*c1*sin_t, 57 | (2*x[1]+1)*cos_t - 0.5*x[0]*c1*sin_t 58 | ]) 59 | 60 | 61 | cont_fig, cont_ax = plt.subplots() 62 | cont_ax.set(xlabel="x", ylabel="y") 63 | cont_ax.set_title("Contour plot with iterates") 64 | cont_ax.contour(x, y, z, levels=30) 65 | 66 | bounds = ((-1, 1), (-2, 2)) 67 | 68 | xnm1 = x0 69 | for i, xn, fxn, grad_xn in descend(func, x0, grad, bounds): 70 | cont_ax.plot([xnm1[0], xn[0]], [xnm1[1], xn[1]], "k*--") 71 | xnm1, grad_xnm1 = xn, grad_xn 72 | 73 | print(f"iterations={i}") 74 | print(f"min val at {xn}") 75 | print(f"min func value = {fxn}") 76 | 77 | 78 | plt.show() -------------------------------------------------------------------------------- /Chapter 09/using-least-squares-to-fit-a-curve-to-data.py: -------------------------------------------------------------------------------- 1 | 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | 5 | from numpy.random import default_rng 6 | rng = default_rng(12345) 7 | 8 | from scipy.optimize import curve_fit 9 | 10 | 11 | 12 | SIZE = 100 13 | x_data = rng.uniform(-3.0, 3.0, size=SIZE) 14 | noise = rng.normal(0.0, 0.8, size=SIZE) 15 | 16 | y_data = 2.0*x_data**2 - 4*x_data + noise 17 | 18 | fig, ax = plt.subplots() 19 | ax.scatter(x_data, y_data) 20 | ax.set(xlabel="x", ylabel="y", title="Scatter plot of sample data") 21 | 22 | fig.savefig("least-squares-scatter-plot.png", dpi=300) 23 | 24 | 25 | def func(x, a, b, c): 26 | return a*x**2 + b*x + c 27 | 28 | coeffs, _ = curve_fit(func, x_data, y_data) 29 | print(coeffs) 30 | # [ 1.99611157 -3.97522213 0.04546998] 31 | 32 | x = np.linspace(-3.0, 3.0, SIZE) 33 | y = func(x, coeffs[0], coeffs[1], coeffs[2]) 34 | ax.plot(x, y, "k--") 35 | 36 | 37 | 38 | plt.show() 39 | fig.savefig("least-squares-best-fit.png", dpi=300) -------------------------------------------------------------------------------- /Chapter 10/accelerating-code-with-cython/mandelbrot/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Applying-Math-with-Python/6fcebc7714d01e9042c218aa21cc92a9541100fb/Chapter 10/accelerating-code-with-cython/mandelbrot/__init__.py -------------------------------------------------------------------------------- /Chapter 10/accelerating-code-with-cython/mandelbrot/cython_mandel.pyx: -------------------------------------------------------------------------------- 1 | 2 | import numpy as np 3 | cimport numpy as np 4 | cimport cython 5 | 6 | ctypedef Py_ssize_t Int 7 | ctypedef np.float64_t Double 8 | 9 | cdef int in_mandel(Double cx, Double cy, int max_iter): 10 | cdef Double x = cx 11 | cdef Double y = cy 12 | cdef Double x2, y2 13 | cdef Int i 14 | for i in range(max_iter): 15 | x2 = x**2 16 | y2 = y**2 17 | if (x2 + y2) >= 4: 18 | return i 19 | y = 2.0*x*y + cy 20 | x = x2 - y2 + cx 21 | return max_iter 22 | 23 | @cython.boundscheck(False) 24 | @cython.wraparound(False) 25 | def compute_mandel(int N_x, int N_y, int N_iter): 26 | cdef double xlim_l = -2.5 27 | cdef double xlim_u = 0.5 28 | cdef double ylim_l = -1.2 29 | cdef double ylim_u = 1.2 30 | 31 | cdef np.ndarray x_vals = np.linspace(xlim_l, xlim_u, N_x, dtype=np.float64) 32 | cdef np.ndarray y_vals = np.linspace(ylim_l, ylim_u, N_y, dtype=np.float64) 33 | 34 | cdef np.ndarray height = np.empty((N_x, N_y), dtype=np.int64) 35 | cdef Int i, j 36 | 37 | for i in range(N_x): 38 | for j in range(N_y): 39 | height[i, j] = in_mandel(x_vals[i], y_vals[j], N_iter) 40 | return height -------------------------------------------------------------------------------- /Chapter 10/accelerating-code-with-cython/mandelbrot/python_mandel.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | def in_mandel(cx, cy, max_iter): 4 | x = cx 5 | y = cy 6 | for i in range(max_iter): 7 | x2 = x**2 8 | y2 = y**2 9 | if (x2 + y2) >= 4: 10 | return i 11 | y = 2.0*x*y + cy 12 | x = x2 - y2 + cx 13 | return max_iter 14 | 15 | def compute_mandel(N_x, N_y, N_iter): 16 | xlim_l = -2.5 17 | xlim_u = 0.5 18 | ylim_l = -1.2 19 | ylim_u = 1.2 20 | x_vals = np.linspace(xlim_l, xlim_u, N_x, dtype=np.float64) 21 | y_vals = np.linspace(ylim_l, ylim_u, N_y, dtype=np.float64) 22 | 23 | height = np.empty((N_x, N_y), dtype=np.int64) 24 | for i in range(N_x): 25 | for j in range(N_y): 26 | height[i, j] = in_mandel(x_vals[i], y_vals[j], N_iter) 27 | return height -------------------------------------------------------------------------------- /Chapter 10/accelerating-code-with-cython/mandelbrot/setup.py: -------------------------------------------------------------------------------- 1 | 2 | import numpy as np 3 | from setuptools import setup, Extension 4 | from Cython.Build import cythonize 5 | 6 | hybrid = Extension( 7 | "hybrid_mandel", 8 | sources=["python_mandel.py"], 9 | include_dirs=[np.get_include()], 10 | define_macros=[("NPY_NO_DEPRECATED_API", "NPY_1_7_API_VERSION")] 11 | ) 12 | 13 | cython = Extension( 14 | "cython_mandel", 15 | sources=["cython_mandel.pyx"], 16 | include_dirs=[np.get_include()], 17 | define_macros=[("NPY_NO_DEPRECATED_API", "NPY_1_7_API_VERSION")] 18 | ) 19 | 20 | extensions = [hybrid, cython] 21 | 22 | setup( 23 | ext_modules = cythonize(extensions, compiler_directives={"language_level": "3"}), 24 | ) -------------------------------------------------------------------------------- /Chapter 10/accelerating-code-with-cython/run.py: -------------------------------------------------------------------------------- 1 | from time import time 2 | from functools import wraps 3 | import matplotlib.pyplot as plt 4 | 5 | from mandelbrot.python_mandel import compute_mandel as compute_mandel_py 6 | from mandelbrot.hybrid_mandel import compute_mandel as compute_mandel_hy 7 | from mandelbrot.cython_mandel import compute_mandel as compute_mandel_cy 8 | 9 | def timer(func, name): 10 | @wraps(func) 11 | def wrapper(*args, **kwargs): 12 | t_start = time() 13 | val = func(*args, **kwargs) 14 | t_end = time() 15 | print(f"Time taken for {name}: {t_end - t_start}") 16 | return val 17 | return wrapper 18 | 19 | mandel_py = timer(compute_mandel_py, "Python") 20 | mandel_hy = timer(compute_mandel_hy, "Hybrid") 21 | mandel_cy = timer(compute_mandel_cy, "Cython") 22 | 23 | Nx = 320 24 | Ny = 240 25 | steps = 255 26 | 27 | mandel_py(Nx, Ny, steps) 28 | mandel_hy(Nx, Ny, steps) 29 | vals = mandel_cy(Nx, Ny, steps) 30 | 31 | fig, ax = plt.subplots() 32 | ax.imshow(vals.T, extent=(-2.5, 0.5, -1.2, 1.2)) 33 | 34 | plt.show() -------------------------------------------------------------------------------- /Chapter 10/accouting-for-uncertainty-in-calculations.py: -------------------------------------------------------------------------------- 1 | from uncertainties import ufloat, umath 2 | 3 | seconds = ufloat(3.0, 0.4) 4 | print(seconds) # 3.0+/-0.4 5 | 6 | 7 | depth = 0.5*9.81*seconds*seconds 8 | print(depth) # 44+/-12 9 | 10 | other_depth = ufloat(44, 12) 11 | time = umath.sqrt(2.0*other_depth/9.81) 12 | print("Estimated time", time) 13 | # Estimated time 3.0+/-0.4 14 | 15 | -------------------------------------------------------------------------------- /Chapter 10/distributing-computations-with-dask.py: -------------------------------------------------------------------------------- 1 | 2 | import dask.dataframe as dd 3 | 4 | 5 | data = dd.read_csv("sample.csv") 6 | print(data.head()) 7 | 8 | 9 | sum_data = data.lower + data.upper 10 | print(sum_data) 11 | 12 | result = sum_data.compute() 13 | print(result.head()) 14 | 15 | 16 | 17 | means = data.loc[:, ("lower", "upper")].mean().compute() 18 | print(means) -------------------------------------------------------------------------------- /Chapter 10/keeping-track-of-units-with-pint.py: -------------------------------------------------------------------------------- 1 | 2 | import pint 3 | 4 | 5 | ureg = pint.UnitRegistry(system="mks") 6 | 7 | 8 | distance = 5280 * ureg.feet 9 | print(distance.to("miles")) 10 | print(distance.to_base_units()) 11 | print(distance.to_base_units().to_compact()) 12 | 13 | @ureg.wraps(ureg.meter, ureg.second) 14 | def calc_depth(dropping_time): 15 | # s = u*t + 0.5*a*t*t 16 | # u = 0, a = 9.81 17 | return 0.5*9.81*dropping_time*dropping_time 18 | 19 | 20 | depth = calc_depth(0.05 * ureg.minute) 21 | print("Depth", depth) 22 | # Depth 44.144999999999996 meter -------------------------------------------------------------------------------- /Chapter 10/loading-and-storing-data-from-netcdf.py: -------------------------------------------------------------------------------- 1 | 2 | import xarray as xr 3 | import pandas as pd 4 | import numpy as np 5 | import matplotlib.pyplot as plt 6 | 7 | from numpy.random import default_rng 8 | rng = default_rng(12345) 9 | 10 | 11 | dates = pd.date_range("2020-01-01", periods=365, name="date") 12 | locations = list(range(25)) 13 | steps = rng.normal(0, 1, size=(365,25)) 14 | accumulated = np.add.accumulate(steps) 15 | 16 | data_array = xr.Dataset({ 17 | "steps": (("date", "location"), steps), 18 | "accumulated": (("date", "location"), accumulated) 19 | }, 20 | {"location": locations, "date": dates} 21 | ) 22 | 23 | print(data_array) 24 | # 25 | # Dimensions: (date: 365, location: 25) 26 | # Coordinates: 27 | # * location (location) int64 0 1 2 3 4 5 6 7 8 ... 17 18 19 20 21 22 23 24 28 | # * date (date) datetime64[ns] 2020-01-01 2020-01-02 ... 2020-12-30 29 | # Data variables: 30 | # steps (date, location) float64 -1.424 1.264 ... -0.4547 -0.4873 31 | # accumulated (date, location) float64 -1.424 1.264 -0.8707 ... 8.935 -3.525 32 | 33 | 34 | means = data_array.mean(dim="location") 35 | 36 | fig, ax = plt.subplots(tight_layout=True) 37 | means["accumulated"].to_dataframe().plot(ax=ax) 38 | ax.set(title="Mean accumulated values", xlabel="date", ylabel="value") 39 | 40 | plt.show() 41 | 42 | 43 | data_array.to_netcdf("data.nc") 44 | 45 | 46 | new_data = xr.load_dataset("data.nc") 47 | print(new_data) 48 | # 49 | # Dimensions: (date: 365, location: 25) 50 | # Coordinates: 51 | # * location (location) int64 0 1 2 3 4 5 6 7 8 ... 17 18 19 20 21 22 23 24 52 | # * date (date) datetime64[ns] 2020-01-01 2020-01-02 ... 2020-12-30 53 | # Data variables: 54 | # steps (date, location) float64 -1.424 1.264 ... -0.4547 -0.4873 55 | # accumulated (date, location) float64 -1.424 1.264 -0.8707 ... 8.935 -3.525 -------------------------------------------------------------------------------- /Chapter 10/sample.csv: -------------------------------------------------------------------------------- 1 | id,number,lower,upper 2 | row0,0,-0.5453279550656607,-0.36648332058049427 3 | row1,1,0.5947309146654682,0.3525093415019491 4 | row2,2,-0.217780898796182,-0.33437214426723094 5 | row3,3,0.19661750717437965,-0.6265316287925733 6 | row4,4,0.3455120880292426,0.8836057305398743 7 | row5,5,-0.503508570740858,0.8977623036666365 8 | row6,6,0.3344749062007448,-0.8082041288117758 9 | row7,7,-0.11632066766437443,0.7729598386550354 10 | row8,8,0.3949069997640442,-0.3470542718597758 11 | row9,9,0.467856326660133,-0.5597300889090275 12 | row10,10,-0.8368108609155838,-0.680208797849905 13 | row11,11,-1.3197996300905894,-0.06961369259589811 14 | row12,12,-0.46715794341845807,0.6315528068496139 15 | row13,13,-0.613411221421011,-0.7410618476455995 16 | row14,14,-0.8166704969101282,0.19713602732982638 17 | row15,15,0.7094838087480027,0.20324248338742623 18 | row16,16,0.863976722271967,0.44956272218404014 19 | row17,17,0.7211026347865848,0.8586756031506326 20 | row18,None,0.09237201816470608,0.8753459175355138 21 | row19,19,-0.010024119842351409,-0.452453635020025 22 | row20,20,-0.09644258505047865,0.3300778467990606 23 | row21,21,-0.3382181390658907,0.8069080136164781 24 | row22,22,-0.4858516494469314,-0.32034332477936034 25 | row23,23,-0.48229320271414533,-0.28910704011142796 26 | row24,24,-0.9899553325657364,0.2572090881993574 27 | row25,25,-0.43523458514976343,-0.8638246210241085 28 | row26,26,0.23365795451276106,-0.6473473594375931 29 | row27,27,-0.3912232255608208,-0.11822637824776394 30 | row28,28,-0.6995953178745984,-0.56414227382913 31 | row29,29,-0.051333769332911006,-0.04726228983761627 32 | row30,30,-0.48953529236099946,-0.40486946370390386 33 | row31,31,-0.4418657603724667,-0.4788415750174049 34 | row32,32,-0.034476814401368516,-1.5760419272969788 35 | row33,33,-0.008738806653918685,-0.5074773483385249 36 | row34,34,0.6769653049338895,-0.6397388198099299 37 | row35,35,0.7243125830184729,-0.6434011103096251 38 | row36,36,0.5010626638744882,0.2222408076611304 39 | row37,37,-0.5816899301427854,0.5197448422479904 40 | row38,38,-0.501478860930175,-0.828856536026884 41 | row39,39,0.2361134446361819,0.07393666206467109 42 | row40,40,0.2690534224305514,-0.6512517826172235 43 | row41,41,-0.5036710202870951,0.36964596927879834 44 | row42,42,-0.838256707498185,0.7501472015122523 45 | row43,43,-0.14261123692001632,0.23678839079475567 46 | row44,44,-0.3737889916297603,-0.6420742894142648 47 | row45,45,-0.9805757444090948,-0.579914083103094 48 | row46,46,0.7400013575433042,0.9456596047951173 49 | row47,47,-0.11641531361779522,-0.2425010103814933 50 | row48,48,-0.4481058374636997,0.9322082184688836 51 | row49,49,-0.883594789468312,-0.1825322022762903 52 | row50,50,-0.6627423109430279,-0.5197118831966243 53 | row51,51,0.5600157126684238,-0.5924648076887753 54 | row52,52,0.10410190952324339,-0.26601171589666506 55 | row53,53,0.014563442784713665,-0.33312440660742837 56 | row54,54,-0.4345566517842432,-0.43633939738958594 57 | row55,55,-0.8292374157574245,-0.03637268843862951 58 | row56,56,0.7666857890291086,0.8944555320200425 59 | row57,57,-0.9452325561457624,0.8355044890051131 60 | row58,58,-0.7569509410291808,0.4956955195716175 61 | row59,59,0.7930414840993898,-0.6641404001256874 62 | row60,60,-0.3370735614663771,-0.2436867487683949 63 | row61,61,-0.306302084651618,0.03251140223072535 64 | row62,62,-0.9820119452573393,-0.15464359103523306 65 | row63,63,1.7553154590232336,-0.8251896942950534 66 | row64,64,-0.031830365083926226,-0.03754454531893958 67 | row65,65,0.5651429806397303,0.9291191502767817 68 | row66,66,0.4141928832710864,-0.45252655816900744 69 | row67,67,0.34022660093971635,-0.3049303945035231 70 | row68,68,0.5362556721098188,0.3515428325049723 71 | row69,69,0.9550640560545192,0.7334195791892422 72 | row70,70,-0.9077839707081079,-0.4193525738247359 73 | row71,71,0.7247821953781601,0.20169566808153072 74 | row72,72,-0.3114840830298182,-0.8887948464221092 75 | row73,73,0.5257453399641963,-0.9969624245218904 76 | row74,74,-0.814312921521728,0.9870083196112802 77 | row75,75,0.5520261544573772,0.7365417258524953 78 | row76,76,-0.04568349682654338,-0.6623027191553501 79 | row77,77,0.06795464229033121,-0.1483362463468949 80 | row78,78,-0.6739408067896355,-0.6682448575985709 81 | row79,79,-0.48369966334835657,0.8861831397315372 82 | row80,80,0.9736138836952428,0.40733282972103124 83 | row81,81,0.4463148267419048,0.4649956144843359 84 | row82,82,0.03479103702911046,-0.6451473834892649 85 | row83,83,0.7552924138551387,0.7607807114991783 86 | row84,84,0.419069105579164,0.8668576143141729 87 | row85,85,0.9599963207988473,0.006951564264227272 88 | row86,86,0.5151390139169887,0.23323704687810198 89 | row87,87,-0.7702218635789513,-0.36802303975273887 90 | row88,88,-0.8645322049402049,0.7737423562224535 91 | row89,89,-0.9558592022501617,0.07457340680881064 92 | row90,90,-0.6447757870185391,-0.7060101812706867 93 | row91,91,-0.6683110398703005,0.7539079578178016 94 | row92,92,-0.011059748841883543,-0.31819900933172107 95 | row93,93,0.9984196517634787,0.09194979150319749 96 | row94,94,-0.39333774878924466,-0.25869659832069414 97 | row95,95,0.3917337561065648,-0.7595068627225243 98 | row96,96,0.5164423763375303,0.7675352136978346 99 | row97,97,-0.04973771044592468,-0.1253905900675356 100 | row98,98,-0.7137704534622713,-0.15451548318042696 101 | row99,99,0.5288233887583287,-0.1720334171281932 102 | -------------------------------------------------------------------------------- /Chapter 10/sample.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Sample Jupyter notebook\n", 8 | "This is a sample notebook." 9 | ] 10 | }, 11 | { 12 | "cell_type": "code", 13 | "execution_count": null, 14 | "metadata": {}, 15 | "outputs": [], 16 | "source": [ 17 | "import matplotlib.pyplot as plt\n", 18 | "from numpy.random import default_rng\n", 19 | "rng = default_rng(12345)" 20 | ] 21 | }, 22 | { 23 | "cell_type": "code", 24 | "execution_count": null, 25 | "metadata": {}, 26 | "outputs": [], 27 | "source": [ 28 | "uniform_data = rng.uniform(-5, 5, size=(2, 100))" 29 | ] 30 | }, 31 | { 32 | "cell_type": "code", 33 | "execution_count": null, 34 | "metadata": {}, 35 | "outputs": [], 36 | "source": [ 37 | "fig, ax = plt.subplots(tight_layout=True)\n", 38 | "ax.scatter(uniform_data[0, :], uniform_data[1, :])\n", 39 | "ax.set(title=\"Scatter plot\", xlabel=\"x\", ylabel=\"y\")" 40 | ] 41 | } 42 | ], 43 | "metadata": { 44 | "language_info": { 45 | "codemirror_mode": { 46 | "name": "ipython", 47 | "version": 3 48 | }, 49 | "file_extension": ".py", 50 | "mimetype": "text/x-python", 51 | "name": "python", 52 | "nbconvert_exporter": "python", 53 | "pygments_lexer": "ipython3", 54 | "version": "3.8.2-final" 55 | }, 56 | "orig_nbformat": 2, 57 | "kernelspec": { 58 | "name": "python38264bit701ec369e6034f5b8233813c354be1bc", 59 | "display_name": "Python 3.8.2 64-bit" 60 | } 61 | }, 62 | "nbformat": 4, 63 | "nbformat_minor": 2 64 | } -------------------------------------------------------------------------------- /Chapter 10/validating-data.py: -------------------------------------------------------------------------------- 1 | import csv 2 | import cerberus 3 | 4 | float_schema = {"type": "float", "coerce": float, "min": -1.0, "max": 1.0} 5 | 6 | item_schema = { 7 | "type": "dict", 8 | "schema": { 9 | "id": {"type": "string"}, 10 | "number": {"type": "integer", "coerce": int}, 11 | "lower": float_schema, 12 | "upper": float_schema, 13 | } 14 | } 15 | 16 | schema = { 17 | "rows": { 18 | "type": "list", 19 | "schema": item_schema 20 | } 21 | } 22 | 23 | 24 | validator = cerberus.Validator(schema) 25 | 26 | 27 | with open("sample.csv") as f: 28 | dr = csv.DictReader(f) 29 | document = {"rows": list(dr)} 30 | 31 | 32 | validator.validate(document) 33 | 34 | errors = validator.errors["rows"][0] 35 | 36 | for row_n, errs in errors.items(): 37 | print(f"row {row_n}: {errs}") 38 | 39 | -------------------------------------------------------------------------------- /Chapter 10/working-with-data-streams.py: -------------------------------------------------------------------------------- 1 | import faust 2 | 3 | from numpy.random import default_rng 4 | rng = default_rng(12345) 5 | 6 | 7 | app = faust.App("sample", broker="kafka://localhost") 8 | 9 | 10 | class Record(faust.Record, serializer="json"): 11 | id_string: str 12 | value: float 13 | 14 | topic = app.topic("sample-topic", value_type=Record) 15 | 16 | @app.agent(topic) 17 | async def process_record(records): 18 | async for record in records: 19 | print(f"Got {record.id_string}: {record.value}") 20 | 21 | 22 | @app.timer(interval=1.0) 23 | async def producer1(app): 24 | await app.send( 25 | "sample-topic", 26 | value=Record(id_string="producer 1", value=rng.uniform(0, 2)) 27 | ) 28 | 29 | @app.timer(interval=2.0) 30 | async def producer2(app): 31 | await app.send( 32 | "sample-topic", 33 | value=Record(id_string="producer 2", value=rng.uniform(0, 5)) 34 | ) 35 | 36 | 37 | 38 | app.main() -------------------------------------------------------------------------------- /Chapter 10/working-with-geographical-data.py: -------------------------------------------------------------------------------- 1 | import geopandas 2 | import geoplot 3 | import matplotlib.pyplot as plt 4 | 5 | world = geopandas.read_file( 6 | geopandas.datasets.get_path("naturalearth_lowres") 7 | ) 8 | 9 | cities = geopandas.read_file( 10 | geopandas.datasets.get_path("naturalearth_cities") 11 | ) 12 | 13 | fig, ax = plt.subplots() 14 | geoplot.polyplot(world, ax=ax) 15 | 16 | 17 | geoplot.pointplot(cities, ax=ax, fc="r", marker="2") 18 | ax.axis((-180, 180, -90, 90)) 19 | 20 | 21 | plt.show() -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Packt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # Applying Math with Python 5 | 6 | Applying Math with Python 7 | 8 | This is the code repository for [Applying Math with Python](https://www.packtpub.com/programming/applying-math-with-python?utm_source=github&utm_medium=repository&utm_campaign=9781838989750), published by Packt. 9 | 10 | **Practical recipes for solving computational math problems using Python programming and its libraries** 11 | 12 | ## What is this book about? 13 | Python, one of the world's most popular programming languages, has a number of powerful packages to help you tackle complex mathematical problems in a simple and efficient way. These core capabilities help programmers pave the way for building exciting applications in various domains such as machine learning and data science, using knowledge in the computational mathematics domain. 14 | 15 | This book covers the following exciting features: 16 | * Get familiar with basic packages, tools, and libraries in Python for solving mathematical problems 17 | * Explore various techniques that will help you to solve computational mathematical problems 18 | * Understand the core concepts of applied mathematics and how you can apply them in computer science 19 | * Discover how to choose the most suitable package, tool, or technique to solve a certain problem 20 | * Implement basic mathematical plotting, change plot styles, and add labels to the plots using Matplotlib 21 | 22 | If you feel this book is for you, get your [copy](https://www.amazon.com/dp/1838989757) today! 23 | 24 | https://www.packtpub.com/ 25 | 26 | ## Instructions and Navigations 27 | All of the code is organized into folders. For example, Chapter02. 28 | 29 | The code will look like the following: 30 | ``` 31 | if (test expression) 32 | { 33 | Statement upon condition is true 34 | } 35 | ``` 36 | 37 | **Following is what you need for this book:** 38 | This book is for professional programmers and students looking to solve mathematical problems computationally using Python. Advanced mathematics knowledge is not a requirement, but a basic knowledge of mathematics will help you to get the most out of this book. The book assumes familiarity with Python concepts of data structures. 39 | 40 | With the following software and hardware list you can run all code files present in the book (Chapter 1-10). 41 | 42 | ### Software and Hardware List 43 | 44 | | Chapter | Software required | OS required | 45 | | -------- | ------------------------------------| -----------------------------------| 46 | | 1 to 10 | Python 3.6 or above | Windows, Mac OS X, and Linux (Any) | 47 | | 10 | Apache Kafka | Windows, Mac OS X, and Linux (Any) | 48 | 49 | ## Code in Action 50 | 51 | Click on the following link to see the Code in Action: 52 | 53 | [YouTube](https://www.youtube.com/playlist?list=PLeLcvrwLe186jdI_AZlqik9ds1g0Cke86) 54 | 55 | ### Related products 56 | * Python for Finance Cookbook [[Packt]](https://www.packtpub.com/data/python-for-finance-cookbook?utm_source=github&utm_medium=repository&utm_campaign=9781789618518) [[Amazon]](https://www.amazon.com/dp/1789618517) 57 | 58 | * The Python Workshop [[Packt]](https://www.packtpub.com/programming/the-python-workshop?utm_source=github&utm_medium=repository&utm_campaign=9781839218859) [[Amazon]](https://www.amazon.com/dp/1839218851) 59 | 60 | ## Get to Know the Author 61 | **Sam Morley** 62 | is an experienced lecturer in mathematics and a researcher in pure mathematics. He is currently a research software engineer at the University of Oxford working on the DataSig project. He was previously a lecturer in mathematics at the University of East Anglia and Nottingham Trent University. His research interests lie in functional analysis, especially Banach algebras. Sam has a firm commitment to providing high-quality, inclusive, and enjoyable teaching, with the aim of inspiring his students and spreading his enthusiasm for mathematics. 63 | 64 | ### Suggestions and Feedback 65 | [Click here](https://docs.google.com/forms/d/e/1FAIpQLSdy7dATC6QmEL81FIUuymZ0Wy9vH1jHkvpY57OiMeKGqib_Ow/viewform) if you have any feedback or suggestions. 66 | ### Download a free PDF 67 | 68 | If you have already purchased a print or Kindle version of this book, you can get a DRM-free PDF version at no cost.
Simply click on the link to claim your free PDF.
69 |

https://packt.link/free-ebook/9781838989750

--------------------------------------------------------------------------------