import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

data = pd.read_csv('height_weight.csv',usecols=['height','weight'])

#Co-Variance - Checking variance of one column with respect to another
#It has both variance and correlation combined into 1 matrix
#           (       a,a         a,b  )
#          (    Var         Var       )
#   Cov = (                            )
#          (       b,a         b,b    )
#           (   Var         Var     )
print(data.head())
print()

"""
print("Incorrect covariance")
covariance = np.cov(data)
print(covariance) #this expects each row and column to be a different variable
"""
print()
print()
#correct it by transposing or rowvar=False
covariance = np.cov(data.T) # or covariance = np.cov(data,rowvar=False)
print("Covariance")
print(covariance)
print()
print()
print("Covariance")
covariance_pandas = data.cov()
print(covariance_pandas)
print()
print()

#CORRELATION formula
#https://www.thoughtco.com/how-to-calculate-the-correlation-coefficient-3126228
#np.corrcoef or pd.DataFrame.corr
#Value of correlation goes from -1 to 1
# 1 = one variable goes up other does too
# -1 = One variable going up and other going down

corref = np.corrcoef(data.T)
print("Correlation")
print(corref)
print()
print("Pandas Correlation")
print()
print(data.corr())
# If variable can be derived from one another they are correlated


import numpy as np
from si.util.scale import StandardScaler

class PCA:

    def __init__(self, ncomponents = 2, using = "svd"):
        # ncomponents must be int
        if ncomponents > 0 and isinstance(ncomponents, int):
            self.ncomponents = round(ncomponents)
        else:
            raise Exception("Number of components must be non negative and an integer")
        self.type = using

    def transform(self, dataset):
        scaled = StandardScaler().fit_transform(dataset).X.T       # scale the features/standardize data

        # using numpy.linalg.svd:
        if self.type.lower()  == "svd":
            self.u, self.s, self.vh = np.linalg.svd(scaled)
        else:
            self.cov_matrix = np.cov(scaled)                       # covariance matrix
            # s are eigenvalues, u are eigenvectors
            self.s, self.u = np.linalg.eig(self.cov_matrix)        # Compute the eigenvalues and eigenvectors
        self.idx = np.argsort(self.s)[::-1]                        # sort the indexes (descending order)
        self.eigen_val =  self.s[self.idx]                         # reorganize by index
        self.eigen_vect = self.u[:, self.idx]                      # reorganize eigen vectors by column index

        self.sub_set_vect = self.eigen_vect[:, :self.ncomponents]  # ordered vectors with principal components
        return scaled.T.dot(self.sub_set_vect)                     # features scaled . vetores proprios ordenados

    def variance_explained(self):
        # find the explained variance
        sum_ = np.sum(self.eigen_val)
        percentage = [i / sum_ * 100 for i in self.eigen_val]       # percentagem da var explicada valor próprio / soma dos valores próprios * 100
        return np.array(percentage)

    def fit_transform(self, dataset):
        trans = self.transform(dataset)
        exp = self.variance_explained()
        return trans, exp


# Various Wind Calculations
#   Change wind speed and direction to U and V components
#   Change U and V components to direction
#   Change U and V components to speed
#   Calculate the angle between two vecors [u1,v1] and [u2,v2]

import numpy as np

def wind_spddir_to_uv(wspd,wdir):
    """
    calculated the u and v wind components from wind speed and direction
    Input:
        wspd: wind speed
        wdir: wind direction
    Output:
        u: u wind component
        v: v wind component
    """

    rad = 4.0*np.arctan(1)/180.
    u = -wspd*np.sin(rad*wdir)
    v = -wspd*np.cos(rad*wdir)

    return u,v

def wind_uv_to_dir(U,V):
    """
    Calculates the wind direction from the u and v component of wind.
    Takes into account the wind direction coordinates is different than the
    trig unit circle coordinate. If the wind directin is 360 then returns zero
    (by %360)
    Inputs:
      U = west/east direction (wind from the west is positive, from the east is negative)
      V = south/noth direction (wind from the south is positive, from the north is negative)
    """
    WDIR= (270-np.rad2deg(np.arctan2(V,U)))%360
    return WDIR

def wind_uv_to_spd(U,V):
    """
    Calculates the wind speed from the u and v wind components
    Inputs:
      U = west/east direction (wind from the west is positive, from the east is negative)
      V = south/noth direction (wind from the south is positive, from the north is negative)
    """
    WSPD = np.sqrt(np.square(U)+np.square(V))
    return WSPD

# Below is used for calculing the angle between two wind vectors
def unit_vector(vector):
    """ Returns the unit vector of the vector.  """
    return vector / np.linalg.norm(vector)

def angle_between(v1, v2):
    """
    Calcualates the angle between two wind vecotrs. Utilizes the cos equation:
                cos(theta) = (u dot v)/(magnitude(u) dot magnitude(v))

    Input:
        v1 = vector 1. A numpy array, list, or tuple with
             u in the first index and v in the second --> vector1 = [u1,v1]
        v2 = vector 2. A numpy array, list, or tuple with
             u in the first index and v in the second --> vector2 = [u2,v2]
    Output:
    Returns the angle in radians between vectors 'v1' and 'v2'::
            >>> angle_between((1, 0, 0), (0, 1, 0))
            1.5707963267948966
            >>> angle_between((1, 0, 0), (1, 0, 0))
            0.0
            >>> angle_between((1, 0, 0), (-1, 0, 0))
            3.141592653589793
    """
    v1_u = unit_vector(v1)
    v2_u = unit_vector(v2)
    angle = np.arccos(np.dot(v1_u, v2_u))
    if np.isnan(angle):
        if (v1_u == v2_u).all():
            return np.rad2deg(0.0)
        else:
            return np.rad2deg(np.pi)
    return np.rad2deg(angle)

#--- Example -----------------------------------------------------------------#
if __name__ == "__main__":
    u = np.array([1,5,-9])
    v = np.array([-1,-2,5])

    vector1 = [u[0],v[0]]
    vector2 = [u[1],v[1]]
    vector3 = [u[2],v[2]]

    print "U component: ", u
    print "V component: ", v
    print "Wind Directions: ", wind_uv_to_dir(u,v)
    print "Wind Speeds: ", wind_uv_to_spd(u,v)
    print ""
    print "Angle between vector 1 and 2: ", angle_between(vector1,vector2)
    print "Angle between vector 2 and 3: ", angle_between(vector2,vector3)


from numpy import linspace, sin, pi, absolute
from numpy.fft import rfft, rfftfreq
import matplotlib.pyplot as plt

# Sampling rate
fs = 100  # Hz
# the total lenght of the wave
length = 1  # 2 second
N = int(fs * length)
# time
t = linspace(0, length, num=N, endpoint=False)

# Generate a sinusoid at frequency f
f = 10  # Hz
a = 5*sin(2 * pi * f * t)
# generate a composite waveform which has 2 different frequencies
#f2 = 30  # Hz
#a = 2* sin(2 * pi * f * t) + 2/3*sin(2 * pi * f2 * t)

# Plot signal, showing how endpoints wrap from one chunk to the next
plt.subplot(2, 1, 1)
plt.plot(t, a, '.-')
plt.plot(1, 1, 'r.')  # first sample of next chunk
plt.margins(0.1, 0.1)
plt.xlabel('Time [s]')
plt.axhline()
plt.axvline()
plt.axvline(x=1/f, c='r', marker=".")

# Use RFFT to get the amplitude of the one-sided spectrum
ampl = 1/N * absolute(rfft(a))
# RFFT frequency bins
freqs = rfftfreq(N, 1/fs)
# Plot spectrum
plt.subplot(2, 1, 2)
plt.stem(freqs, ampl)
plt.margins(0.1, 0.1)
plt.xlabel('Frequency [Hz]')
plt.tight_layout()
plt.show()

# Question 1:
# think about how to plot sum of two sine waveform
#

# Solution:
# uncomment Line 17&18, change f and f2 to get different results


# Objective is to predict blood pressure from age & weight.
# R-sq values are calcualted for all combination of inputs (age only, weight only & both)
# Data is from:
# http://college.cengage.com/mathematics/brase/understandable_statistics/7e/students/datasets/mlr/frames/mlr02.html

# The data (X1, X2, X3) are for each patient.
# X0 = systolic blood pressure
# X1 = age in years
# X2 = weight in pounds

# Code Flow:
    # 1. Import all relevant libraries.
    # 2. Load the dataset using pandas (X - input/feature, Y - output/target).
    # 3. Plot the generated data understand the trend.
    # 4. Create input features for all combinations.
    # 5. get_r2 function defintion that calculates weights, predictions & final r-sq values.
    # 6. Print results for all cases.

# 1. Imports
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd

# 2. Load dataset:
df = pd.read_excel('mlr02.xls')
X = df.as_matrix()

# 3. Plot the data to understand the trends:
plt.figure(1)
plt.scatter(X[:,1],X[:,0])
plt.xlabel('Age')
plt.ylabel('Systolic Blood Pressure')
plt.title('Blood pressure vs. Age')

plt.figure(2)
plt.scatter(X[:,2],X[:,0])
plt.xlabel('Weight (pounds)')
plt.ylabel('Systolic Blood Pressure')
plt.title('Blood pressure vs. Weight (pounds)')

# 4. Create input features for all combinations:
df['ones'] = 1

Y = df['X1']
X = df[['X2','X3','ones']]
X2only = df[['X2','ones']]
X3only = df[['X3','ones']]

# 5. Function definition that calcualtes weights, predictions & R-sq values:
def get_r2(X,Y):
    w = np.linalg.solve(np.dot(X.T,X),np.dot(X.T,Y))
    Yhat = np.dot(X,w)
    # R-sq:
    d1 = Y - Yhat
    d2 = Y - Y.mean()
    r2 = 1 - d1.dot(d1)/d2.dot(d2)

    return r2

# 6. Print results:
print('the r-squared for x2 only is:',get_r2(X2only,Y))
print('the r-squared for x3 only is:',get_r2(X3only,Y))
print('the r-squared for x2 & x3 only is:',get_r2(X,Y))

1.Linear Regression/1.Code - Using Theory/3.Blood Pressure Prediction.py


"""
円周率
"""

def pi(digit: int) -> str:
	from sympy import N, atan
	return str(N("((12 * atan(1/49)) + (32 * atan(1/57)) - (5 * atan(1/239)) + (12 * atan(1/110443))) * 4", digit))

if __name__ == "__main__":
	digit = 10000
	print(pi(digit))


#https://github.com/QuantConnect/Tutorials/blob/master
from math import log, sqrt, exp, pi
from scipy.stats import norm

class BsmModel:

    def __init__(self, option_type, price, strike, interest_rate, expiry, volatility, dividend_yield=0):
        self.s = price  # Underlying asset price
        self.k = strike  # Option strike K
        self.r = interest_rate  # Continuous risk fee rate
        self.q = dividend_yield  # Dividend continuous rate
        self.T = expiry  # time to expiry (year)
        self.sigma = volatility  # Underlying volatility
        self.type = option_type # option type "p" put option "c" call option

    def n(self, d):
        # cumulative probability distribution function of standard normal distribution
        return norm.cdf(d)

    def dn(self, d):
        # the first order derivative of n(d)
        return norm.pdf(d)

    def d1(self):
        d1 = (log(self.s / self.k) + (self.r - self.q + self.sigma ** 2 * 0.5) * self.T) / (self.sigma * sqrt(self.T))
        return d1

    def d2(self):
        d2 = (log(self.s / self.k) + (self.r - self.q - self.sigma ** 2 * 0.5) * self.T) / (self.sigma * sqrt(self.T))
        return d2

    def bsm_price(self):
        d1 = self.d1()
        d2 = d1 - self.sigma * sqrt(self.T)
        if self.type == 'c':
            price = exp(-self.r*self.T) * (self.s * exp((self.r - self.q)*self.T) * self.n(d1) - self.k * self.n(d2))
            return price
        elif self.type == 'p':
            price = exp(-self.r*self.T) * (self.k * self.n(-d2) - (self.s * exp((self.r - self.q)*self.T) * self.n(-d1)))
            return price
        else:
            print("option type can only be c or p")

a = BsmModel('c', 42, 35, 0.1, 90.0/365, 0.2)
price = a.bsm_price()


import numpy as np
import matplotlib.pyplot as plt

def gaussian(x, mean, std):
    std2 = np.power(std, 2)
    return (1 / np.sqrt(2* np.pi * std2)) * np.exp(-.5 * (x - mean)**2 / std2)

if __name__ == "__main__":
    gauss_1 = gaussian(10, 8, 2) # 0.12098536225957168
    gauss_2 = gaussian(10, 10, 2) # 0.19947114020071635

    print("Gauss(10, 8, 2): {}".format(gauss_1))
    print("Gauss(10, 10, 2): {}".format(gauss_2))

    # 標準高斯分佈
    mean = 0
    variance = 1
    std = np.sqrt(variance)

    # Plot between -10 and 10 with .001 steps.
    x = np.arange(-5, 5, 0.001)
    gauss = []
    for i in x:
        gauss.append(gaussian(i, mean, std))
    gauss = np.array(gauss)

    plt.plot(x, gauss)
    plt.show()

Sensor Fusion and Tracking/Kalman Filters/Gaussian/gaussian.py


import numpy as np
import numpy.linalg as linalg
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

def center_data(raw_data):
	'''
	Given raw_data as numpy array, where a row
	is an observation. Return centered data

	Input: raw_data (numpy array)
	Returns: centered_data (numpy array)
	'''

	col_mean = raw_data.mean(axis=0)
	centered_data = raw_data - col_mean
	return centered_data

def graph_data3D(data, fig_name):

	fig = plt.figure()

	ax = fig.add_subplot(111, projection='3d')

	color_list = ['r', 'g', 'b', 'y']

	for c in range(4):
		b = data[:,-1] == c
		x0 = data[:, 0][b]
		x1 = data[:, 1][b]
		x2 = data[:, 2][b]

		ax.scatter(x0, x1, x2, color=color_list[c])

	plt.savefig(fig_name)

def graph_data2D(data, groups, fig_name):

	color_list = ['r', 'g', 'b', 'y']

	for c in range(4):
		b = groups == c
		x0 = data[:, 0][b]
		x1 = data[:, 1][b]

		plt.scatter(x0, x1, color=color_list[c])

	plt.xlim((data[:,0].min(), data[:,0].max()))
	plt.ylim((data[:,1].min(), data[:,1].max()))
	plt.savefig(fig_name)

def do_pca(data, comp_num):
	'''
	Return original data mapped
	to first comp_num of princ components

	Inputs:
		- data: (numpy array)
		- comp_num: (int) of prin comp
	Returns:
		- proj_data: (numpy array) of projected data
	'''

	centered_data = center_data(data)
	var_cov = centered_data.T @ centered_data * (1/centered_data.shape[0])

	# note: e_vecs will have COLUMNs as the corresponding eigenvectors
	# note: the e_vecs are already normalized, per documentation
	e_vals, e_vecs = linalg.eig(var_cov)

	e_ordering = np.argsort(e_vals)[::-1]
	e_basis = e_vecs[e_ordering][:, 0:comp_num]

	coeff = centered_data @ e_basis

	return coeff

def do_LLE(data, out_dim, k):
	'''
	Does local linear embedding with k nearest neighbors
	Each ROW in data is an observation
	'''

	# Loop over each observation in the data
	N = data.shape[0]

	w_matrix = np.zeros( (N, N) )

	w_dict = {}

	for i in range(N):

		# Calculate the weights for that observation i
		cur_obs = data[i,:]
		local_data = data - cur_obs
		local_distances = linalg.norm(local_data, axis=1)
		assert local_distances.shape == (N,)

		ordering = np.argsort(local_distances)
		assert ordering[0] == i
		k_nn_indices = ordering[1:k+1]

		neighborhood = local_data[k_nn_indices]

		# create the local Gram matrix
		K_i = neighborhood @ neighborhood.T
		ones = np.ones((k,1))

		# Solve for w_i matrix and normalize
		w_i = linalg.inv(K_i) @ ones
		w_i = w_i / linalg.norm(w_i, axis=0)

		# we need to input the KNN w_i avlues into w_matrix
		#print("{} NN of pt {} are:".format(k, cur_obs))
		for j in range(k):
			neighbor_index = k_nn_indices[j]
			neighbor_weight = w_i[j]
			w_matrix[i,neighbor_index] = neighbor_weight
			#print("\t pt {}".format(data[neighbor_index,:]))

	# Construct matrix M from the sparse w_matrix
	n_I = np.identity(N)
	M = (n_I - w_matrix).T @ (n_I - w_matrix)

	e_vals, e_vecs = linalg.eigh(M)
	proj = e_vecs[:, 1:1+out_dim]

	return proj

def do_iso(data, k, proj_dim):

	N = data.shape[0]

	# step1: calcualte the pairwise distances
	pairwise_distances = np.empty( (N,N) )
	for i in range(N):
		for j in range(N):
			x_i = data[i,:]
			x_j = data[j,:]
			diff = x_i - x_j
			norm_diff = linalg.norm(diff)
			pairwise_distances[i,j] = norm_diff

	# step2: calculate the nearest neighbors for each column
	dist_w_inf = np.full( (N,N), np.inf)

	for i in range(N):
		dist_i = pairwise_distances[:,i]
		ordered_indices = np.argsort(dist_i)

		knn_indices = ordered_indices[0:k+1]
		dist_w_inf[knn_indices, i] = pairwise_distances[knn_indices, i]

	# step3: compute shortest path
	# Apply Floyd-Warshall algorithm
	short_path_dist = dist_w_inf
	for k in range(N):
		for i in range(N):
			for j in range(N):

				if short_path_dist[i,j] > short_path_dist[i,k] + short_path_dist[k,j]:
					short_path_dist[i,j] = short_path_dist[i,k] + short_path_dist[k,j]

	# Do eigen decomp on shortest paths
	e_vals, e_vecs = linalg.eigh(short_path_dist)
	flipped_e_vals = np.flip(e_vals, axis=0)
	flipped_e_vecs = np.flip(e_vecs, axis=1)

	# step4: now that the eigens are sorted largest>smallest take first p, take sq.root
	flipped_e_vals[proj_dim:] = 0
	lambda_sqrt = np.sqrt(flipped_e_vals)

	# step5: construct diagonal matrix
	diag_lambda = np.diagflat(lambda_sqrt)
	y_matrix = (flipped_e_vecs @ diag_lambda).T

	return pairwise_distances, dist_w_inf, short_path_dist, y_matrix

if __name__ == "__main__":

	# Load data
	orig_data = np.loadtxt('data/3Ddata.txt')
	orig_data[:, 3] = orig_data[:, 3] - 1
	data_3d = orig_data[:,0:3]
	centered_data = center_data(data_3d)

	var_cov = centered_data.T @ centered_data * (1/centered_data.shape[0])

	# A: Do pca
	pca_coeff = do_pca(data_3d, 2)
	graph_data3D(orig_data, "3d_data.png")
	plt.clf()
	graph_data2D(pca_coeff, orig_data[:,-1], "pca.png")
	plt.clf()

	# B: do ISOMAP
	pairwise_distances, dist_w_inf, short_path_dist, y_matrix  = do_iso(data_3d, 10, 2)
	y0 = y_matrix[0,:]
	y1 = y_matrix[1,:]
	group = orig_data[:,-1]

	color_list = ['r', 'g', 'b', 'y']

	for c in range(4):
		b = group == c

		plt.scatter(y0[b], y1[b], color=color_list[c])
	plt.savefig('isomap.png')
	plt.clf()

	# C: do LLE
	lle_proj = do_LLE(data_3d, 2, 10)
	graph_data2D(lle_proj, orig_data[:,-1], "lle.png")
	plt.clf()


from typing import Tuple, Optional

import numpy as np
import scipy.stats
import matplotlib.pyplot as plt

def beta_a_b_from_mode_concentration(mode: float, concentration: float) -> Tuple[float, float]:
	"""
	Computes the shape parameters, alpha and beta, of a beta distribution given its mode and concentration.

	Parameters
	----------
	mode : float
		The mode of the distribution
	concentration : float
		The concentration of the distribution

	Returns
	-------
	out: tuple of floats
		alpha and beta parameters

	"""

	return mode*(concentration - 2.) + 1., (1-mode)*(concentration - 2.) + 1.

def beta_mode_concentration_from_a_b(a: float, b: float) -> Tuple[float, float]:
	"""
	Computes the mode and concentration of a beta distribution from its shape parameters, alpha and beta.

	Parameters
	----------
	a : float
		alpha parameter
	b : float
		beta parameter

	Returns
	-------
	out: tuple of floats
		Mode and concentration

	"""

	assert a > 1 and b > 1
	return (a - 1.) / (a + b - 2.), a + b

def plot_beta(
		a: Optional[float] = None, b: Optional[float] = None, mode: Optional[float] = None,
		concentration: Optional[float] = None) -> None:
	"""
	Plots a beta distribution.

	Parameters
	----------
	a : float, optional
		alpha parameter
	b : float, optional
		beta parameter
	mode : float, optional
		Mode
	concentration : float, optional
		Concentration

	"""

	if mode and concentration:

		a, b = beta_a_b_from_mode_concentration(mode, concentration)

	elif a and b:

		# it's fine
		pass

	else:

		raise Exception('either `a` and `b` or `mode` and `concentration` must be passed')

	x = np.linspace(scipy.stats.beta.ppf(0.01, a, b), scipy.stats.beta.ppf(0.99, a, b), 100)

	plt.figure()
	plt.plot(x, scipy.stats.beta.pdf(x, a, b), 'r-', lw=5, alpha=0.6, label='beta pdf')

def gamma_shape_rate_from_mode_sd(mode: float, sd: float) -> Tuple[float, float]:
	"""
	Returns the shape and rate parameters of a gamma distribution from its mode and standard deviation.

	Parameters
	----------
	mode : float
		Mode
	sd : float
		Standard deviation

	Returns
	-------
	out: tuple of floats
		Shape and rate

	"""

	r = (mode + np.sqrt(mode**2 + 4*sd**2)) / (2*sd**2)
	s = 1 + mode * r

	return s, r

def plot_gamma(mode: float, sd: float) -> None:
	"""
	Plots a gamma distribution.

	Parameters
	----------
	mode : float
		Mode
	sd : float
		Standard deviation

	"""

	shape, rate = gamma_shape_rate_from_mode_sd(mode, sd)

	# the scale is the inverse of the rate
	scale = 1./rate

	x = np.linspace(scipy.stats.gamma.ppf(0.01, a=shape, scale=scale), scipy.stats.gamma.ppf(0.99, a=shape, scale=scale))

	plt.figure()
	plt.plot(x, scipy.stats.gamma.pdf(x, a=shape, scale=scale), 'r-', lw=5, alpha=0.6, label='gamma pdf')

def plot_half_cauchy(scale: float) -> None:
	"""
	Plot a half-cauchy distribution.

	Parameters
	----------
	scale : float
		Scale

	"""

	x = np.linspace(scipy.stats.halfcauchy.ppf(0.01, scale=scale), scipy.stats.halfcauchy.ppf(0.99, scale=scale), 100)

	plt.figure()
	plt.plot(x, scipy.stats.halfcauchy.pdf(x, scale=scale), 'r-', lw=5, alpha=0.6, label='Half-Cauchy pdf')


# Project 1 Main - Computational Physics
# Hunter Phillips

# This program allows the user to input n, a, b, and c to solve
# Poisson's equation in a single dimension by turning it into a matrix.
# using Gaussian Jordan Elimination, Specialized Tridiagonal, and LU Decomposition
# Benchmarks and error calculations are also performed.

import sys
import numpy as nmp
from solver import *

# part b
def part_b(n, a_i, b_i, c_i):
    a = nmp.full(n+2, a_i, dtype=nmp.float64)
    b = nmp.full(n+2, b_i, dtype=nmp.float64)
    c = nmp.full(n+2, c_i, dtype=nmp.float64)
    x, v = general(f_function, a, b, c, n)
    return x, v

# part c
def part_c(n, a_i, b_i, c_i):
    a = nmp.full(n+2, a_i, dtype=nmp.float64)
    b = nmp.full(n+2, b_i, dtype=nmp.float64)
    c = nmp.full(n+2, c_i, dtype=nmp.float64)
    x, v = tridiag(f_function, a, b, c, n)
    return x, v

# part d
def part_d(n, a_i, b_i, c_i):
    a = nmp.full(n+2, a_i, dtype=nmp.float64)
    b = nmp.full(n+2, b_i, dtype=nmp.float64)
    c = nmp.full(n+2, c_i, dtype=nmp.float64)
    x, v = tridiag(f_function, a, b, c, n)
    u = u_function(x)
    eps_i = nmp.log10(abs((v[1:-1]-u[1:-1])/u[1:-1])) # strip the last and first parts of matrix
    eps = nmp.max(eps_i)
    return n, eps

# part e
def part_e(n, a_i, b_i, c_i):
    a = a_i
    b = b_i
    c = c_i
    x, v = LU(f_function, a, b, c, n)
    return  x, v

# benchmarks
def benchmarking(n, a_i, b_i, c_i):

    a = nmp.full(n+2, a_i, dtype=nmp.float64) # have to get that accuracy
    b = nmp.full(n+2, b_i, dtype=nmp.float64)
    c = nmp.full(n+2, c_i, dtype=nmp.float64)

    start_time = time.time() # in the future I would like to add more complex timing analysis
    x, v = general(f_function, a, b, c, n)
    end_time = time.time()
    general_time = end_time - start_time

    start_time = time.time()
    x, v = tridiag(f_function, a, b, c, n)
    end_time = time.time()
    tridiag_time = end_time - start_time

    a = a_i
    b = b_i
    c = c_i

    LU_time = 0. # preallocation

    if (n <= 10000):
        start_time = time.time()
        x, v = LU(f_function, a, b, c, n)
        end_time = time.time()
        LU_time = end_time - start_time

    return general_time, tridiag_time, LU_time

if __name__ == "__main__":

    print('This program allows the user to input n (for n x n matrix), a, b, and c to solve\nPoisson\'s equation in a single dimension by turning it into a matrix\nusing Gaussian Jordan Elimination, Specialized Tridiagonal, and LU Decomposition\nBenchmarks and error calculations are also performed.\n\n')

    # get user input
    n_i = int(raw_input('Please input the size n of your desired matrix: '))
    a_i = int(raw_input('Please input the value of a for your desired matrix: '))
    b_i = int(raw_input('Please input the value of b for your desired matrix: '))
    c_i = int(raw_input('Please input the value of c for your desired matrix: '))

    print('\nGeneral Algorithm')
    b_x, b_v = part_b(n_i, a_i, b_i, c_i)
    print(b_x)
    print(b_v)

    print('\nSpecialized Tridiagonal Algorithm')
    c_x, c_v = part_c(n_i, a_i, b_i, c_i)
    print(c_x)
    print(c_v)

    print('\nRelative Error')
    n, eps = part_d(n_i, a_i, b_i, c_i)
    print('n = ' + str(n))
    print('error = ' + str(eps))

    print('\nLinear Decomposition Algorithm')
    e_x, e_v = part_e(n_i, a_i, b_i, c_i)
    print(e_x)
    print(e_v)

    general_time, tridiag_time, LU_time = benchmarking(n_i, a_i, b_i, c_i)
    print('\nGeneral Algorithm Time to Execute')
    print(general_time)
    print('\nSpecialized Tridiagonal Algorithm Time to Execute')
    print(tridiag_time)
    print('\nLU Decomposition Time to Execute')
    print(LU_time)
    print('\n\n')


"""
Animated example of Newton-Raphson method for finding roots

MIT License

Copyright (c) 2021 Luiz Gustavo da Rocha Charamba

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
"""

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib import animation

def f(x):
  return x**6/6 - 3*x**4 - 2*x**3/3 + 27*x**2/2 + 18*x - 30

def d_f(x):
  return x**5 - 12*x**3 -2*x**2 + 27*x + 18

def newton_raphson(x):
    return x - f(x) / d_f(x)

x = 5.0#-6.5#-6.0

d = {"x" : [x], "f(x)": [f(x)]}
nr_X = []
nr_Y = []
iterations = 200
for i in range(0, iterations):
  x = newton_raphson(x)
  d["x"].append(x)
  nr_X.append(x)
  d["f(x)"].append(f(x))
  nr_Y.append(f(x))

print("Iterations: ", len(nr_X))
print(pd.DataFrame(d, columns=['x', 'f(x)']))

X = []
Y = []
left_limit = -5
right_limit = 5
step_sample = 0.01

for x in np.arange(left_limit, right_limit, step_sample):
    y = f(x)
    X.append(x)
    Y.append(y)

# First set up the figure, the axis, and the plot element we want to animate
fig = plt.figure()
ax = plt.axes(xlim=(-5,5), ylim=(-70, 1000))
f_line, = ax.plot([],[]) #ax.plot(X, Y)
root_line, = ax.plot([],[], color='r', marker='o') #ax.plot(nr_X, nr_Y, color='r', marker='o')

# initialization function: plot the background of each frame
def init():
    f_line.set_data([], [])
    root_line.set_data([], [])

    return f_line, root_line,

def animate(i):
    f_line.set_data(X, Y)
    root_line.set_data(np.array(nr_X[:i]), np.array(nr_Y[:i]))
    return f_line, root_line

anim = animation.FuncAnimation(fig, animate, init_func=init,
                               frames=200, interval=200, blit=True)

plt.show()


#!/usr/bin/python3
import sympy
import math
from prettytable import PrettyTable
x = sympy.Symbol('x')
ln = sympy.ln

f = x * ln(x) - x
g = x-f/f.diff(x)
tol = 0.1
xa = 2.5
table = PrettyTable()

iters = []
funcG = []
funcF = []
errorTot = []
deriv = []

def newton(tol,xa,niter):
    fx = f.evalf().subs({x:xa})
    funcF.append("%e" % (fx))
    error = tol+1
    errorTot.append("")
    funcG.append((xa))
    deriv.append(f.diff(x).evalf().subs({x:xa}))
    iters.append(0)
    cont = 0
    while(fx != 0 and error > tol and cont < niter):
        xn = g.evalf().subs({x:xa}).evalf()
        funcG.append((xn))
        deriv.append(f.diff(x).evalf().subs({x:xn}).evalf())
        fx = f.evalf().subs({x:xn}).evalf()
        funcF.append("%e" % (fx))
        error = abs(xn-xa)
        errorTot.append("%e" % (error/xn))
        xa = xn
        cont = cont +1
        iters.append(cont)
    if ( fx == 0):
        print(str(xa)+" es raiz")
    elif (error < tol):
        print(str(xa)+" es una aproximacion ")
    else:
        print("El metodo fracasó")
    table.add_column("n",iters)
    table.add_column("xn",funcG)
    table.add_column("f(xn)",funcF)
    table.add_column("f'(xn)",deriv)
    table.add_column("error",errorTot)
    print(table)

newton(tol,xa,200)


# -*- coding: utf-8 -*-
"""
@author: salimt
"""
#Problem 2: R^2
#10/10 points (graded)
#After we create some regression models, we also want to be able to evaluate our models to figure out how well each model represents our data, and tell good models from poorly fitting ones. One way to evaluate how well the model describes the data is computing the model's R^2 value. R^2 provides a measure of how well the total variation of samples is explained by the model.
#
#Implement the function r_squared. This function will take in:
#
#list, y, that represents the y-coordinates of the original data samples
#estimated, which is a corresponding list of y-coordinates estimated from the regression model
#This function should return the computed R^2 value. You can compute R^2 as follows, where  is the estimated y value for the i-th data point (i.e. predicted by the regression),  is the y value for the ith data point, and  is the mean of the original data samples.
#
#
#If you are still confused about R^2 , its wikipedia(https://en.wikipedia.org/wiki/Coefficient_of_determination) page has a good explanation about its use/how to calculate it.
#
#Note: If you want to use numpy arrays, you should import numpy as np and use np.METHOD_NAME in your code. Unfortunately, pylab does not work with the grader.
#
##Problem 2
#
def r_squared(y, estimated):
    """
    Calculate the R-squared error term.
    Args:
        y: list with length N, representing the y-coords of N sample points
        estimated: a list of values estimated by the regression model
    Returns:
        a float for the R-squared error term
    """
    import numpy

    error = ((numpy.array(estimated) - numpy.array(y))**2).sum()
    meanError = error/len(y)
#    return round(1 - (meanError/numpy.var(y)), 4)
    return 1 - (meanError/numpy.var(y))

#print(r_squared([32.0, 42.0, 31.3, 22.0, 33.0], [32.3, 42.1, 31.2, 22.1, 34.0])) #0.9944


# -*- coding: utf-8 -*-
# Imports all the necessary libraries
"""This application approximates the PI value based on a Monte Carlo Simulation.
It asks for input from the user regarding on number of darts thrown."""
import math
import random
import numpy as np
import matplotlib.pyplot as plt

def gen_circle(radius=1, center_x=0, center_y=0, res=50):
    """Returns the points of coordinates [x,y] which lie on the circle
    with the radius radius and with the center specified at center = [x,y],
    with the resolution res"""
    xcord = [center_x + radius * math.cos(tht) for \
         tht in np.linspace(0, 2 * math.pi, res)]
    ycord = [center_y + radius * math.sin(tht) for \
         tht in np.linspace(0, 2 * math.pi, res)]
    return [xcord, ycord]

def gen_square(center_x=0, center_y=0, edge=2):
    """Returns the vertices of a square in [x,y] coordinates, which is centered
    at the center=[x,y] and with the edge length equal to edge"""
    vertices_x = [center_x + edge/2, center_x - \
                  edge/2, center_x - edge/2, center_x + edge/2]
    vertices_y = [center_y + edge/2, center_y + \
                  edge/2, center_y - edge/2, center_y - edge/2]
    vertices = [vertices_x, vertices_y]
    return vertices

PLAY = True
APPROX_PI_LIST = list()
NUM_DARTS_LIST = list()

while PLAY:
    #Insert the number of darts to be thrown
    NUM_DARTS = int( \
            input('Please insert the number of darts that you want to throw\n'))
    NUM_DARTS_LIST.append(NUM_DARTS)

    CIRC_DARTS = {'x':list(), 'y':list()}
    SQUARE_DARTS = {'x':list(), 'y':list()}

    CIRC_HITS = 0
    SQUARE_HITS = 0

    for dart in range(NUM_DARTS):
        x_dart = random.random() * 2 - 1
        y_dart = random.random() * 2 - 1

        if(x_dart**2 + y_dart**2)**(1/2) > 1:
            SQUARE_HITS += 1
            SQUARE_DARTS['x'].append(x_dart)
            SQUARE_DARTS['y'].append(y_dart)

        elif(x_dart**2 + y_dart**2)**(1/2) <= 1:
            CIRC_HITS += 1
            CIRC_DARTS['x'].append(x_dart)
            CIRC_DARTS['y'].append(y_dart)

        else:
            pass

    APPROX_PI = 4 * CIRC_HITS/NUM_DARTS #THE APPROXIMATED VALUE OF PI
    APPROX_PI_LIST.append(APPROX_PI)

    #Plots the darts thrown in a new figure
    plt.figure(figsize=(10, 10))

    #Plots the square
    plt.fill(gen_square()[0], gen_square()[1], fill=False)

    #Plots the circle
    plt.plot(gen_circle()[0], gen_circle()[1], 'g-')

    #Plots the darts which landed on the darts board
    plt.plot(CIRC_DARTS['x'], CIRC_DARTS['y'], 'go')

    #Plots the darts which landed outside the darts board and inside the square
    plt.plot(SQUARE_DARTS['x'], SQUARE_DARTS['y'], 'ro')

    #Sets a title to the plot
    plt.title('Approximated Pi value: {:6.4f} ==> Darts thrown: {:d}'.format( \
            APPROX_PI, NUM_DARTS), {'fontsize':20})

    #Turning off hte tick labels for the plots
    plt.xticks([])
    plt.yticks([])

    print("\nYour darts Monte Carlo method approximative value of PI is:",
          " {}\n\nThat's {} off from the actual value of PI\n".format(\
            APPROX_PI, abs(math.pi - APPROX_PI)))
    print('\nCircle hit {} times and square hit {} times'.format(\
          CIRC_HITS, SQUARE_HITS))

    QUESTION_STRING = 'Do you want to trow darts again Y/N?. \
    To see all the plots press N\n'
    CONTINUE_PLAY = input(QUESTION_STRING)
    if CONTINUE_PLAY == 'Y':
        PLAY = True
    elif CONTINUE_PLAY == 'N':
        PLAY = False
        print('\nThank you for playing the game,',
              ' have a look at all the plots generated\n')
        print('\nThe previous results are:\n')

        for piVal, dart in zip(APPROX_PI_LIST, NUM_DARTS_LIST):
            print(f'Approximated Pi value: {piVal:4} ',
                  f'==> Darts thrown: {dart:4}')


from sympy import symbols, sqrt, solve
from scipy.signal.wavelets import cascade
import matplotlib.pyplot as plt
import numpy as np
import utilities.wavelet as wv

def compute_A3_filter_coefficeints():

    h = symbols('h0 h1 h2 h3 h4 h5')

    # Filter Equations
    eq1 = sum(v**2 for v in h) - 1
    eq2 = sum([h[i-2]*h[i] for i in range(2, len(h))])
    eq3 = sum(h) - sqrt(2)

    # A(3) Equations
    eq4 = sum([(-1)**i*h[i] for i in range(0, len(h))])
    eq5 = sum([(-1)**i*(-i)*h[i] for i in range(0, len(h))])
    eq6 = sum([(-1)**i*i**2*h[i] for i in range(0, len(h))])

    solutions = solve([eq1, eq2, eq3, eq4, eq5, eq6], *h)
    print solutions

def plot_A3_symbol(show=True):
    h = [0.332670552950083, 0.806891509311093, 0.459877502118492, -
         0.135011020010255, -0.0854412738820267, 0.0352262918857095]

    # Compute Symbol
    P, f = wv.compute_symbol(h)

    plt.figure(1)

    # Plot Symbol
    t = np.linspace(0, 1, 200)
    plt.plot(t, [abs(P.subs(f, v).evalf()) for v in t])
    plt.xlabel('f')
    plt.ylabel('abs(P)')
    plt.title('Symbol P(f)')

    if show:
        plt.show()

def plot_A3_scaling_function_and_wavelet(show=True):
    h = [0.332670552950083, 0.806891509311093, 0.459877502118492, -
         0.135011020010255, -0.0854412738820267, 0.0352262918857095]

    plt.figure(2)

    t, phi, psi = cascade(h)
    plt.subplot(1,2,1)
    plt.plot(t, phi)
    plt.xlabel('t')
    plt.ylabel('phi')
    plt.title('scaling function')

    plt.subplot(1,2,2)
    plt.plot(t, psi)
    plt.xlabel('t')
    plt.ylabel('psi')
    plt.title('mother wavelet')

    if show:
        plt.show()

if __name__ == "__main__":
    plot_A3_symbol(show=False)
    plot_A3_scaling_function_and_wavelet()


import numpy as np
import matplotlib.pyplot as plt

# linear function
def linear(m, b, x):
    return m * x + b

# Sum of Squared Errors
def sse(m: float, b: float, x: np.ndarray, y: np.ndarray) -> float:
    guess = linear(m, b, x)
    actual = y

    return sum((actual - guess) ** 2) / 2

# partial derivative of sse() w.r.t 'm'
def dm_sse(m: float, b: float, x: np.ndarray, y: np.ndarray):
    return sum(-x * (y - linear(m, b, x)))

# partial derivative of sse() w.r.t 'b'
def db_sse(m: float, b: float, x: np.ndarray, y: np.ndarray):
    return sum(-(y - linear(m, b, x)))

X: np.ndarray = np.linspace(0, 20, 30)
Y: np.ndarray = X + np.random.random(30) * 5 - 10

M = 0.0  # slope
B = 0.0  # intercept

# higher learning rate might cause uncontrolled bouncing & will not converge
learning_rate = 0.0001

# Gradient descent until Squared error is below 35
while sse(M, B, X, Y) > 35:
    M -= dm_sse(M, B, X, Y) * learning_rate
    B -= db_sse(M, B, X, Y) * learning_rate

print(M, B, sse(M, B, X, Y))

fitted_x = np.linspace(0, 20, 30)
fitted_y = linear(M, B, fitted_x)

plt.plot(fitted_x, fitted_y)
plt.scatter(X, Y)
plt.show()


"""
Utility functions for testing different search
algorithms implemented in this skill.
"""
import signal
import numpy as np

from functools import wraps

class LossFunctions:
    """
    Loss functions to evaluate the performance
    of search algorithms. All methods available in
    this class take numpy arrays as input.

    Methods
    -------
    mape:
        Mean averaged percentage error.
    rmse:
        Root mean squared error.
    mse:
        Mean squared error.
    """
    @staticmethod
    def mape(A, B):
        """
        Calculates the mean absolute persentage error
        from two series. Original solution from:

            https://stats.stackexchange.com/questions/58391/\
                mean-absolute-percentage-error-mape-in-scikit-learn

        Parameters
        ----------
        A, B: numpy.array
            Numpy arrays with the same length with the
            two objects representing the results (A)
            and test data (B).

        Returns
        -------
        float
            Floating number in the domain [0, 1].
        """
        #
        #  Let's add 1 to all values
        #  to avoid division by zero.
        #
        A += 0.000000000001
        B += 0.000000000001
        return np.round(np.mean(np.abs(A - B) / A) * 100, 2)

    @staticmethod
    def rmse(A, B):
        """
        Calculates the root mean square error from
        two series. Original solution from:

            https://stackoverflow.com/questions/16774849\
                /mean-squared-error-in-numpy

        Parameters
        ----------
        A, B: numpy.array
            Numpy arrays with the same length with the
            two objects representing the results (A)
            and test data (B).

        Returns
        -------
        float
            Floating number in the same domain
            of the original data.
        """
        return np.round(np.sqrt(np.square(np.subtract(A, B)).mean()), 2)

    @staticmethod
    def mse(A, B):
        """
        Calculates the mean square error from
        two series. Original solution from:

            https://stackoverflow.com/questions/16774849\
                /mean-squared-error-in-numpy

        Parameters
        ----------
        A, B: numpy.array
            Numpy arrays with the same length with the
            two objects representing the results (A)
            and test data (B).

        Returns
        -------
        float
            Floating number in the squared domain
            of the original data.
        """
        return np.round(np.square(np.subtract(A, B)).mean(), 2)


# Implementing Newton Raphson method in Python
# Author: Syed Haseeb Shah (github.com/QuantumNovice)
# The Newton-Raphson method (also known as Newton's method) is a way to
# quickly find a good approximation for the root of a real-valued function

from decimal import Decimal
from math import *  # noqa: F401, F403
from sympy import diff

def newton_raphson(func: str, a: int, precision: int=10 ** -10) -> float:
    """ Finds root from the point 'a' onwards by Newton-Raphson method
    >>> newton_raphson("sin(x)", 2)
    3.1415926536808043
    >>> newton_raphson("x**2 - 5*x +2", 0.4)
    0.4384471871911695
    >>> newton_raphson("x**2 - 5", 0.1)
    2.23606797749979
    >>> newton_raphson("log(x)- 1", 2)
    2.718281828458938
    """
    x = a
    while True:
        x = Decimal(x) - (Decimal(eval(func)) / Decimal(eval(str(diff(func)))))
        # This number dictates the accuracy of the answer
        if abs(eval(func)) < precision:
            return float(x)

# Let's Execute
if __name__ == "__main__":
    # Find root of trigonometric function
    # Find value of pi
    print(f"The root of sin(x) = 0 is {newton_raphson('sin(x)', 2)}")
    # Find root of polynomial
    print(f"The root of x**2 - 5*x + 2 = 0 is {newton_raphson('x**2 - 5*x + 2', 0.4)}")
    # Find Square Root of 5
    print(f"The root of log(x) - 1 = 0 is {newton_raphson('log(x) - 1', 2)}")
    # Exponential Roots
    print(f"The root of exp(x) - 1 = 0 is {newton_raphson('exp(x) - 1', 0)}")


# Illustrate the powr method for computing largest eigenvector

import numpy as np
from numpy.linalg import norm

np.random.seed(0)

def power_method(A, max_iter=100, tol=1e-5):
    n = np.shape(A)[0]
    u = np.random.rand(n)
    converged = False
    iter = 0
    while (not converged) and (iter < max_iter):
        old_u = u
        u = np.dot(A, u)
        u = u / norm(u)
        lam = np.dot(u, np.dot(A, u))
        converged = (norm(u - old_u) < tol)
        iter += 1
    return lam, u

X = np.random.randn(10, 5)
A = np.dot(X.T, X)
lam, u = power_method(A)

evals, evecs = np.linalg.eig(A)
idx = np.argsort(np.abs(evals))[::-1] # largest first
evals = evals[idx]
evecs = evecs[:,idx]

tol = 1e-3
assert np.allclose(evecs[:,0], u, tol)
assert np.allclose(evals[0], lam, tol)


import numpy as np
from scipy import optimize
import matplotlib.pyplot as plt
from numba import jit

def moving_averages(x, half_window):
    x = np.append(np.array([x[0]]*half_window), x)
    x = np.append(x, np.array([x[-1]]*half_window))
    cumsum = np.cumsum(np.insert(x, 0, 0))
    window = half_window * 2
    ma = (cumsum[window:-1] - cumsum[:-window-1]) / window
    return ma

@jit
def approx(coeffs, t, period = 1.0):
    dim = int((coeffs.size - 1) / 2)
    a0 = coeffs[0]
    a = coeffs[1:dim+1]
    b = coeffs[dim+1:2*dim+1]
    tt = 2 * np.pi / period * np.arange(1, dim+1) * np.ones((t.size, dim)) * t.reshape((t.size,1))
    res = a0 + np.sum(a * np.sin(tt), axis = 1) + np.sum(b * np.cos(tt), axis = 1)
    # res = a0 \
    #       + sum(map(lambda i : a[i] * np.sin(2 * np.pi * (i+1) / period * t), range(0,dim))) \
    #       + sum(map(lambda i : b[i] * np.cos(2 * np.pi * (i+1) / period * t), range(0,dim)))
    return res

@jit
def approx_scalar(coeffs, t):
    dim = int((coeffs.size - 1) / 2)
    a0 = coeffs[0]
    a = coeffs[1:dim+1]
    b = coeffs[dim+1:2*dim+1]
    tt = 2 * np.pi * np.arange(1, dim+1) * t
    res = a0 + np.sum(a * np.sin(tt)) + np.sum(b * np.cos(tt))
    # res = a0 \
    #       + sum(map(lambda i : a[i] * np.sin(2 * np.pi * (i+1) / period * t), range(0,dim))) \
    #       + sum(map(lambda i : b[i] * np.cos(2 * np.pi * (i+1) / period * t), range(0,dim)))
    return res

class approximation():
    def __init__(self, label, points, values, ma_halfwindow, min_degree, max_degree):
        self.label = label
        self.points = points
        self.values = values
        self.ma_halfwindow = ma_halfwindow
        self.ma = moving_averages(self.values, self.ma_halfwindow)
        self.min_degree = min_degree
        self.max_degree = max_degree
        self.coeffs = []
        self.rmses = []
        for i in range(self.min_degree, self.max_degree+1):
            p0 = np.array([1.0]*(2*i+1))
            result = optimize.leastsq(lambda p: approx(p, self.points) - self.values, p0)
            rmse = np.mean((approx(result[0], self.points) - self.values)**2)
            self.coeffs.append(np.array(result[0]))
            self.rmses.append(rmse)

    def plot_all(self):
        fig, ax1 = plt.subplots()
        ax1.set_ylabel(self.label)
        ax1.scatter(self.points, self.values, color='red', label='values', marker='.')
        ax1.plot(self.points, self.ma, color='red', label='moving average')
        for p in self.coeffs:
            ax1.plot(self.points, approx(p, self.points), label=f'approx of degree {(p.size - 1) / 2}')
        ax1.legend(loc='upper center')
        fig.tight_layout()
        plt.show()

    def plot_rmses(self):
        fig, ax1 = plt.subplots()
        ax1.plot(range(self.min_degree, self.max_degree+1), self.rmses)
        fig.tight_layout()
        plt.show()


"""
# !/usr/bin/env python
# -*- coding: utf-8 -*-
@Time        : 2022/3/26 11:56
@File        : softmax.py
"""

import numpy as np

# Write a function that takes as input a list of numbers, and returns
# the list of values given by the softmax function.
def softmax(L):
    """
    softmax
    """
    exPL = np.exp(L)
    sumExpL = sum(exPL)

    result = []
    for i in exPL:
        result.append(i * 1.0/sumExpL)
    return result

# Note: The function np.divide can also be used here, as follows:
# def softmax(L):
#     expL = np.exp(L)
#     return np.divide (expL, expL.sum())

udacity-program_self_driving_car_engineer_v1.0/part01-computer vision and deep learning/module03-deep learning/lesson01-introduction to neural networks/exercise03-softmax/softmax.py


import numpy as np

def pol2cart(rho, phi):
    """
    Transform from polar to cart coordinates
    :param rho: distance to origin
    :param phi: angle (rad)
    :return: x, y
    """
    x = rho * np.cos(phi)
    y = rho * np.sin(phi)
    return x, y

def cart2pol(x, y):
    """
    Transform from cart to polar coordinates
    :param x: x
    :param y: y
    :return: rho, phi (rad)
    """
    rho = (x * x + y * y) ** 0.5
    phi = np.arctan2(y, x)
    return rho, phi

def pol2cart_ramap(rho, phi):
    """
    Transform from polar to cart under RAMap coordinates
    :param rho: distance to origin
    :param phi: angle (rad) under RAMap coordinates
    :return: x, y
    """
    x = rho * np.sin(phi)
    y = rho * np.cos(phi)
    return x, y

def cart2pol_ramap(x, y):
    """
    Transform from cart to polar under RAMap coordinates
    :param x: x
    :param y: y
    :return: rho, phi (rad) under RAMap coordinates
    """
    rho = (x * x + y * y) ** 0.5
    phi = np.arctan2(x, y)
    return rho, phi


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Fri Feb  8 21:45:48 2019

@author: alankar
"""
import numpy as np
import os

def trapezoidal(func, a, b, n,*args):
    h = float(b - a) / n
    s = 0.0
    s += func(a,*args)/2.0
    for i in range(1, n):
        s += func(a + i*h,*args)
    s += func(b,*args)/2.0
    return s * h

def romberg(func, a, b, n,verbose,*args): #approximation error of order 2n
    file = open('Romberg Triangle.txt','a')
    h = float(b - a)
    T = np.zeros((n+1,n+1))
    if verbose: file.write('Romberg Triangular matrix: N = %d\n'%n)
    for j in range(1,n+1):
        T[j,1] = (h/2)*(func(a,*args)+2*np.sum(func(a+np.arange(h,int(2**(j-1))*h,h),*args))+func(b,*args)) #Comp Trapz
        if verbose: file.write('%f  '%T[j,1])
        for k in range(2,j+1):
            T[j,k] = T[j,k-1]+(T[j,k-1]-T[j-1,k-1])/(4**(k-1)-1) #Richardson Extrp
            if verbose: file.write('%f  '%T[j,k])
        if verbose: file.write('\n')
        h/=2
    if verbose: file.write('\n\n')
    if verbose: file.close()
    return T[n,n]

f = lambda x:np.sin(np.sqrt(100*x))**2

tol = 1e-6

n = 1
I_old = 0
I_new = trapezoidal(f,0,1,n)

print('TRAPEZOIDAL INTEGRATION (ADAPTIVE):')
while (abs(I_new-I_old)>tol):
    n *= 2
    print('Slices = %d'%n)
    I_old = I_new
    I_new = trapezoidal(f,0,1,n)
    print('I = %.7f'%I_new)
    print('Fractional error estimate: %e'%abs((I_new-I_old)/I_new))

n = 1
I_old = 0
I_new = romberg(f,0,1,n,True)
os.remove('Romberg Triangle.txt')

print('\n\nROMBERG INTEGRATION:')
while (abs(I_new-I_old)>tol):
    n *= 2
    print('Slices = %d'%n)
    I_old = I_new
    I_new = romberg(f,0,1,n,True)
    print('I = %.7f'%I_new)
    print('Fractional error estimate: %e'%abs((I_new-I_old)/I_new))

"""
Output:

TRAPEZOIDAL INTEGRATION (ADAPTIVE):
Slices = 2
I = 0.3252319
Fractional error estimate: 5.450032e-01
Slices = 4
I = 0.5122829
Fractional error estimate: 3.651322e-01
Slices = 8
I = 0.4029974
Fractional error estimate: 2.711814e-01
Slices = 16
I = 0.4301034
Fractional error estimate: 6.302188e-02
Slices = 32
I = 0.4484147
Fractional error estimate: 4.083563e-02
Slices = 64
I = 0.4539129
Fractional error estimate: 1.211304e-02
Slices = 128
I = 0.4553485
Fractional error estimate: 3.152691e-03
Slices = 256
I = 0.4557113
Fractional error estimate: 7.960349e-04
Slices = 512
I = 0.4558022
Fractional error estimate: 1.995014e-04
Slices = 1024
I = 0.4558249
Fractional error estimate: 4.990618e-05
Slices = 2048
I = 0.4558306
Fractional error estimate: 1.247847e-05
Slices = 4096
I = 0.4558321
Fractional error estimate: 3.119738e-06
Slices = 8192
I = 0.4558324
Fractional error estimate: 7.799420e-07

ROMBERG INTEGRATION:
Slices = 2
I = 0.3843160
Fractional error estimate: 6.149537e-01
Slices = 4
I = 0.3489739
Fractional error estimate: 1.012746e-01
Slices = 8
I = 0.4558325
Fractional error estimate: 2.344253e-01
Slices = 16
I = 0.4558325
Fractional error estimate: 8.931689e-12
"""


#Find Delannoy Numbers

def getDelannoyArray(n=5):
    colk = [k for k in range(n+1)]
    a=[[ 1 if(j==0 or i==0) else 0 for j in colk] for i in colk]
    a[1][1]=3
    for i in range(1,n+1):
        for j in range(1,n+1):
            a[i][j] = a[i-1][j]+a[i-1][j-1]+a[i][j-1]

    return a

#Delannoy Sequence from array
def DelannoySequenceFT(n=5,returni=False):
    a = getDelannoyArray(n)
    b = [[a[i][j] for j in range(len(a[i])) if(i==j) ][0] for i in range(len(a))]
    if(returni):
        return b
    else:
        print(b)

#Delannoy Sequence from array (method 2)
def DelannoySequenceFT2(n=5,returni=False):
    colk = [k for k in range(n+1)]
    a=[[ 1 if(j==0 or i==0) else 0 for j in colk] for i in colk]
    a[1][1]=3
    b=[]
    for i in range(1,n+1):
        for j in range(1,n+1):
            if(1==1):
                a[i][j] = a[i-1][j]+a[i-1][j-1]+a[i][j-1]
                if(i==j):
                    b.append(a[i][j])
            else:
                continue
    return b

#Central numbers from Delannoy Sequence
def DelannoySequence(n=5,returni=False,roundt=True):
    from sympy import Integer as mmmint
    from sympy import Float as mmmfloat
    a = [1,3]
    for i in range(2,n+2):
        b = (3*(2*i-1)*a[i-1]-(i-1)*a[i-2])/i
        if(roundt==True):
            b = mmmint(b)
        else:
            b = mmmfloat(b)
        a.append(b)
        if(not returni):
            print(b,end=", ")
    if(returni):
        return a[:n]

def DelannoArray(countt=10):
    s = getDelannoyArray(countt)
    r = range(len(s))
    from pandas import DataFrame as mmpd
    df=mmpd(s, columns=r, index=r)
    df=df.rename_axis(columns="n/k")
    return df

#getDelannoyArray()
#DelannoArray()
#DelannoySequenceFT2(2000) # 3.26 s
#DelannoySequenceFT(2000) #3.12 s
#DelannoySequence(2000) #387 ms


#-*-conding:utf-8-*-

import numpy as np
from math import exp, sqrt, log
import random
from scipy.stats.distributions import norm

'''
Parameters:
s0 = initial stock price
k = strike price
r = risk-less short rate
sig = volatility of stock value
dt = t/T = time to maturity
m = the number of path nodes
n = the number of simulation
'''

def black_scholes_model(s0, k, r, sig, dt):
    d1 = (log(s0 / k) + (r + sig ** 2 / 2) * dt) / (sig * sqrt(dt))
    d2 = d1 - sig * sqrt(dt)
    call_bs = s0 * exp(-r * dt) * norm.cdf(d1) - k * exp(-r * dt) * norm.cdf(d2)
    put_bs = k * exp(-r * dt) * norm.cdf(-d2) - s0 * exp(-r * dt) * norm.cdf(-d1)
    return {'call_BS': call_bs, 'put_BS': put_bs}

def monte_carlo_simulation(s0, k, r, sig, dt, m, n):
    list_1 = []  # call option value list
    list_2 = []  # put option value list
    delta_t = dt / m  # length of time interval

    for i in range(0, n):
        path = [s0]
        for j in range(0, m):
            path.append(path[-1] * exp((r - 0.5 * sig ** 2) * delta_t + (sig * sqrt(delta_t) * random.gauss(0, 1))))

        put_value = max(k - path[-1], 0)
        call_value = max(path[-1] - k, 0)
        list_2.append(put_value)
        list_1.append(call_value)
    p = np.average(list_2)
    c = np.average(list_1)
    return {'call_MC': c, 'put_MC': p}

'''
trial:
a = black_scholes_model(5200, 5200, 0.03, 0.25, 0.08)
b = monte_carlo_simulation(5200, 5200, 0.03, 0.25, 0.08, 20, 2000000)

print(a)
print(b)
'''


# Create a numpy array from the weight_lb list with the correct units. Multiply by 0.453592 to go from pounds to kilograms.
# Store the resulting numpy array as np_weight_kg.
# Use np_height_m and np_weight_kg to calculate the BMI of each player.
# Use the following equation:
# BMI = weight(kg) / height (m3)
# save the resulting numpy array as bmi
# Print out bmi.
# height and weight are available as regular lists

# Import numpy
import numpy as np

# Create array from height_in with metric units: np_height_m
np_height_m = np.array(height_in) * 0.0254

# Create array from weight_lb with metric units: np_weight_kg
np_weight_kg = np.array(weight_lb) * 0.453592

# Calculate the BMI: bmi
bmi = np_weight_kg / np_height_m ** 2

# Print out bmi
print(bmi)

01-introduction to python for data science/04-numpy/baseball-players-bmi.py


import numpy as np
from sklearn import linear_model
from sklearn import datasets

class Perceptron():
    def __init__(self,model='prime',max_iter=1000,lamb=10**-2):
        self.model=model
        self.max_iter=max_iter
        self.lamb=lamb

    def train(self,X_train,y_train):
        self.X_train=X_train
        self.y_train=y_train
        train_no,feature_no=X_train.shape
        W=np.zeros((feature_no,1))
        alpha=np.zeros((train_no,1))
        b=0
        flag=True
        iter_no=0
        while flag:
            iter_no+=1
            flag=False
            for i in range(train_no):
                if self.model=='dual':
                    W=np.zeros((feature_no,1))
                    for j in range(train_no):
                        W+=alpha[j]*y_train[j]*X_train[j].reshape(feature_no,1)
                    if y_train[i]*(X_train[i].dot(W)+b)<=0:
                        alpha[i]=alpha[i]+self.lamb
                        b=b+self.lamb*y_train[i]
                        flag=True
                        break
                if self.model=='prime':
                    if y_train[i]*(X_train[i].dot(W)+b)<=0:
                        W=W+self.lamb*y_train[i]*X_train[i].reshape(feature_no,1)
                        b=b+self.lamb*y_train[i]
                        flag=True
                        break

            if iter_no>self.max_iter:
                break
        self.W=W
        self.b=b
        self.alpha=alpha

    def predict(self,X_test):
        train_no,feature_no=self.X_train.shape
        if self.model=='prime':
            y_pred=np.sign(X_test.dot(self.W)+self.b)
        if self.model=='dual':
            W=np.zeros((feature_no,1))
            for j in range(train_no):
                W+=self.alpha[j]*self.y_train[j]*self.X_train[j].reshape(feature_no,1)
            y_pred=np.sign(X_test.dot(W)+self.b)
        return y_pred.ravel()

if __name__=='__main__':
#    X_train=np.array([[3,3],[4,3],[1,1]])
#    y_train=np.array([1,1,-1])
#    X_test=np.array([[100,100],[99,99]])

    index=np.random.permutation(150)
    iris=datasets.load_iris()
    X_train=iris['data'][:,(2,3)]
    y_train=(iris['target']==2).astype(np.float64)
    y_train[y_train==0]=-1
    y_train_1=y_train
    X_test=X_train
    X_train=X_train[index]
    y_train=y_train[index]

    model=Perceptron(model='prime',max_iter=10**3,lamb=10**-4)
    model.train(X_train,y_train)
    y_pred=model.predict(X_test)
    print('Perceptron',y_pred)
    print(sum(y_pred==y_train_1))

    model=linear_model.Perceptron()
    model.fit(X_train,y_train)
    y_pred=model.predict(X_test)
    print('sklearn',y_pred)
    print(sum(y_pred==y_train_1))

Supervised Machine Learning Algorithms/Perceptron.py


## Worksheet 1

import numpy as np
import numpy.linalg as la

# The first worksheet covers basic topics in linear algebra. There is also a basic question on nonlinear root-finding.

#### Answer Coding Question 3

def MatrixConditionCheck(A, MaxConditionNumber = 10.0):
    """Check the condition number of a matrix.
    Only write output to screen if the condition number is too high.
    Should return something, really."""

    ConditionNumber = la.cond(A)
    if ConditionNumber > MaxConditionNumber:
        print("The condition number of the matrix\n{0}\n"\
              "is too large (i.e., it is {1:.4} which is larger"\
              " than {2:.4}).\n".\
              format(A, ConditionNumber, MaxConditionNumber))

#### Answer Coding Question 4

def bisection(f, interval, tolerance = 1.e-10):
    """General bisection method for a function f of one variable.
    There must be at least one root within the interval.
    Default tolerance (width of the interval) is 1e-10."""

    assert len(interval) == 2

    # Get the endpoints of the interval
    [x_min, x_max] = interval

    # Values at the ends of the domain
    f_min = f(x_min)
    f_max = f(x_max)

    # Check that at least one root lies within the interval
    assert(f_min * f_max < 0.0)

    # The loop
    x_c = (x_min + x_max) / 2.0
    f_c = f(x_c)
    iteration = 0
    while ((x_max - x_min > tolerance) and \
               (np.abs(f_c) > tolerance) and \
               (iteration < 100)):
        iteration = iteration+1
        if f_min * f_c < 0.0:
            x_max = x_c
            f_max = f_c
        else:
            x_min = x_c
            f_min = f_c
        x_c = (x_min + x_max) / 2.0
        f_c = f(x_c)

    print("The root is approximately {0} where "\
          "f is {1:.4} (tolerance {2:.4})".format(x_c, f_c, tolerance))
    return x_c

# Now define the function whose root is to be found
def fn_worksheet1_q4(x):
    """Simple function defined in question, f(x) = tan(x) - exp(-x)."""

    return np.tan(x) - np.exp(-x)


# Chapter 7: Cross-Correlations, Fourier Transform, and Wavelet Transform
prepared by Gilbert Chua

Gaining a deeper undersanding of time series dynamics and classifying them, we look at time series forecasting through another lens.

In this chapter, we will take a different approach to how we analzye time series that is complementary to forecasting. Previously, methods of explaining such as the Granger Causality, Simplex Mapping, and Convergent Cross Mapping, focused on the time domain - the values of the time series when measured over time or in its phase space. While both are useful for many tasks, it can be often useful to transform these time domain measurements to unearth patterns which are difficult to tease out. Specifically, we want to look at the frequency domain to both analyze the dynamics and perform pre-processing techniques that may be used to modify real-world datasets.

We will be analyzing the dynamics of time series not exactly to make forecasts, but to understand them in terms of their frequencies in complement to the previous methods of causality and explainability presented.

We introduce three techniques:

    1) Cross-correlations

    2) Fourier Transform

    3) Wavelet Transform

and test their use on the Jena Climate Dataset (2009-2016) along with a handful of other datasets.

import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import pandas as pd
from datetime import date, timedelta
import scipy as scp
import random
from statsmodels.tsa.stattools import acf, adfuller, ccf, ccovf
from itertools import repeat
import pywt
import os
import warnings
warnings.filterwarnings('ignore')

## Cross-correlation Between Two Signals

Phenomenon captured by time series may not happen at the exact same time with some lag $h$ between them. A simple example of this is Sonar technology. The time series associated with the response from the sound waves being reflected comes at some lag compared to the time series of the device emitting the initial sound waves. It is this lag we want to measure when we use the cross-correlation function (CCF). This function was discussed previously in Chapter 3 - Vector Autoregressive Methods. We will explore this concept in this chapter and adopt the same definition.

Let $\mathbf\Gamma_h$ be the covariance matrix at lag $h$, $\mathbf D$ be a $N\times N$ diagonal matrix containing the standard deviations of $y_{i,t}$ for $i=1, ..., N$. The lagged correlation matrix of $\mathbf{y}_t$ is defined as

$$\boldsymbol\rho_h = \mathbf D^{-1}\mathbf\Gamma_h\mathbf D^{-1}$$

The $(i,j)$th element of $\boldsymbol\rho_h$ is the correlation coefficient between $y_{i,t}$ and $y_{j,t-h}$:

$$\rho_{i,j}(h) = \dfrac{Cov\left[y_{i,t}, y_{j,t-h} \right]}{\sigma_{i,t} \sigma_{j,t}}$$

where $Cov[y_{i,t}, y_{j,t-h}]$ is the covariance of two time series $y_{i,t}$ and $y_{j,t-h}$ at time $t$ and lag $h$.

The values of the correlation coefficient from the cross-correlation function are interpreted as such:

|Correlation Coefficient| Interpretation |
|:----|:----|
|$\rho_{i,j}(0)\neq0$|$y_{i,t}$ and $y_{j,t}$ are *contemporaneously linearly correlated*|
|$\rho_{i,j}(h)=\rho_{j,i}(h)=0$ for all $h\geq0$|$y_{i,t}$ and $y_{j,t}$ share *no linear relationship*|
|$\rho_{i,j}(h)=0$ and $\rho_{j,i}(h)=0$ for all $h>0$|$y_{i,t}$ and $y_{j,t}$ are said to be linearly *uncoupled*|
|$\rho_{i,j}(h)=0$ for all $h>0$, but $\rho_{j,i}(q)\neq0$ for at least some $q>0$|There is a *unidirectional (linear) relationship* between $y_{i,t}$ and $y_{j,t}$, where $y_{i,t}$ does not depend on $y_{j,t}$, but $y_{j,t}$ depends on (some) lagged values of $y_{i,t}$|
|$\rho_{i,j}(h)\neq0$ for at least some $h>0$ and $\rho_{j,i}(q)\neq0$ for at least some $q>0$|There is a *bi-directional (feedback) linear relationship* between $y_{i,t}$ and $y_{j,t}$|

### Noisy Signals

To demonstrate this, we take the case of two simple yet noisy time series.

We want to measure the shift between two periodic signals Two sine waves are generated that are identical in both amplitude and frequency but one is shifted forward in time. By taking the maximum of the cross-correlation, we should be able to retrieve how much one of the time series leads (or lags!) the other.

def sine_wave(amp=1, freq=1, sample_rate=200, duration=5,
              plot=True, shift=0, noise=0):
    x = np.linspace(0, duration, sample_rate*duration)
    frequencies = x * freq
    y = amp*np.sin((2 * np.pi * frequencies) + shift) + noise
    if plot:
        plt.subplots(figsize=(15, 2))
        plt.plot(x, y)
        plt.show()
    return x, y

samp_rate = 500  # Sampling Frequency in Hertz
dur = 5  # Duration in Seconds

amp1 = 1  # Amplitude of Sine Wave 1
freq1 = 1  # Frequency of Sine Wave 1

amp2 = 1  # Amplitude of Sine Wave 2
freq2 = 1  # Frequency of Sine Wave 2

phase_shift = np.pi/2  # Phase shift
noise = np.random.uniform(-1, 1, samp_rate*dur)
x1, y1 = sine_wave(amp1, freq1, samp_rate, dur, noise=noise,
                   plot=False)
noise = np.random.uniform(-0.3, 0.3, samp_rate*dur)
x2, y2 = sine_wave(amp2, freq2, samp_rate, dur, noise=noise,
                   shift=phase_shift, plot=False)

plt.subplots(figsize=(15, 2))
plt.plot(np.linspace(0, dur, samp_rate*dur), y1, label='$y1$')
plt.plot(np.linspace(0, dur, samp_rate*dur), y2, label='$y2$')
plt.ylabel('Value')
plt.xlabel('Time (in $s$)')
plt.legend();

plt.subplots(figsize=(15, 2))

ccf_12 = ccf(y1, y2, unbiased=False)
plt.ylabel('Correlation Coefficient')
plt.xlabel('Lag $h$')
plt.stem(np.linspace(0, dur, samp_rate*dur), ccf_12)
peak = np.argmax(ccf_12)/samp_rate
plt.axvline(peak, c='orange')
print('Maximum at:', peak, '(in $s$)')

We find that the maximum correlation between $y_2$ and $y_1$ occurs at ~0.247 seconds, approximately 1/4 of the frequency. This is very close to the theoretical value of 0.25 seconds corresponding to a phase shift of $\frac{\pi}{2}$. Using only direct measurements from the data, albeit synthetic, we are able to retrieve a reasonable estimate of the lag and delay between them.

By measuring the cross-correlation between these two signals we can find how far apart we need to adjust the time series to maximize their linear correlation. It is important to note that unlike the different causality measures discussed (Granger, Convergent Cross-mapping, etc.), cross-correlation does **not** give insights into causality - rather it only gives correlations.

### Example 1: Cross-Correlations in the Jena Climate Data

Let us look at how cross-correlations can be used to unearth relationships in real data. We will again use the Jena Climate Dataset to analyze the average temperature. This time, we will look at the relationship between daily temperature and daily wind speed.

df = pd.read_csv('../data/jena_climate_2009_2016.csv')
display(df.head())
data = df.iloc[:, 1:].astype(float).to_numpy()
temp = data[:, 1][::144]  # Temperature (in degrees Celsius) at one sample/day
wind = data[:, 10][::144]  # Wind Speed (in m/s) at one sample/day
dur = len(temp)
samp_rate = 1

plt.subplots(figsize=(15, 2))
plt.plot(range(len(temp)), temp, label='Temperature')
plt.plot(range(len(wind)), wind, label='Wind Velocity')
plt.legend();

We rescale the graphs so that we can easily visualize them.

plt.subplots(figsize=(15, 2))
plt.plot(range(len(temp)), (temp-np.mean(temp)) /
         np.std(temp), label='Temperature')
plt.plot(range(len(wind)), (wind-np.mean(wind)) /
         np.std(wind), label='Wind Velocity')
plt.ylabel('Value')
plt.xlabel('Time (in days)')
plt.legend();

From previous chapters, we know that these time series are stationary. With stationarity, we can proceed with applying the CCF to these two time series.

plt.subplots(figsize=(15, 2))

ccf_12 = ccf(temp, wind, unbiased=False)
plt.ylabel('Correlation Coefficient')
plt.xlabel('Lag $h$')
plt.stem(np.linspace(0, dur, samp_rate*dur), ccf_12)
peak = np.argmax(ccf_12)/samp_rate
plt.axvline(peak, c='r')
print('Maximum at:', peak, '(in $s$)')

We find that the shift maximizing the correlation between the daily values of temeperature and wind speed are 178 samples. An interesting result is that the correlation at $s=0$ is at a minimum. Both of these suggest that temperature and wind speed are periodic functions mirror each other in that when one is maximized, the other is minimized. We estimate then that the wind speed leads the temperature values by 178 days.

It should be noted that unlike Granger Causality which also draws relationships between multiple time series, cross-correlation does not provide us with the causality between the two. At best, cross-correlation shows the synchronocity between two time series.

### Example 2: Cross-Correlations Between Precipitation and Flow in the Sudbury River

We apply cross-correlation analysis on the daily stream flow of a river and the precipitation in its surrounding area. The data will be coming from the United States Geological Survey's (USGS) Real-time Water Data for the Nation and covers the Subdury River at Saxonville, Massachusetts for the year 2000. It comprises of the daily mean flow of water measured by the the USGS station. Understanding the causes and dynamics of flow in this specific river can have profound implications on managing the integity and diversity of its ecosystem.

flow_df = pd.read_csv('../data/cc/flowsud_2000.txt', sep='\t')
flow_df.columns = ['USGS', 'site_no', 'datetime', 'flow', '-']

flow = flow_df['flow']

rain_df = pd.read_csv('../data/cc/weather_2000.txt')
rain_df['Prcp'] = rain_df['Prcp'].replace('T', 0.0005).astype(float)

rain = rain_df['Prcp']

dur = len(rain)
samp_rate = 1

plt.subplots(figsize=(15, 2))
plt.plot(flow, label='Stream Flow')
plt.plot(rain, label='Precipitation')
plt.legend();

Once again, we rescale the graphs so that we can easily visualize them.

plt.subplots(figsize=(15, 2))
plt.plot((rain-np.mean(rain))/np.std(rain), label='Preciptation (exaggerated)')
plt.plot((flow-np.mean(flow))/np.std(flow), label='Stream Flow')
plt.xlabel('Day of the Year')
plt.legend();

We perform the Augmented Dickey-Fuller test on the two time series to figure out if they are stationary. If they are indeed stationary, we can proceed to performing the cross-correlation.

result = adfuller(flow)
print('ADF Statistic: %f' % result[0])
print('p-value: %f' % result[1])
print('Critical Values:')
for key, value in result[4].items():
    print('\t%s: %.3f' % (key, value))

result = adfuller(rain)
print('ADF Statistic: %f' % result[0])
print('p-value: %f' % result[1])
print('Critical Values:')
for key, value in result[4].items():
    print('\t%s: %.3f' % (key, value))

Both time series are stationary and we proceed with applying the CCF.

plt.subplots(figsize=(15, 2))

ccf_12 = ccf(flow, rain, unbiased=False)
plt.ylabel('Correlation Coefficient')
plt.xlabel('Lag $h$')
plt.stem(np.linspace(0, dur, samp_rate*dur), ccf_12)
peak = np.argmax(ccf_12)/samp_rate
plt.axvline(peak, c='r')
print('Maximum at:', peak, '(in $days$)')

We find that the maximum correlation happens when precipitation leads stream flow by 1 day. While cross-correlation alone cannot give us causality, understanding of the measurements involved can be used to argue such. In the case of rainfall and precipitation, there is established understanding that precipitation leads to increases in river flow. We can conclude with some confidence that precipitation will cause an increase in river flow after 1 day.

To supplement our analysis, we move into the frequency domain with the Fourier Transform by first understanding the Fourier Series.

## The Fourier Series:

Any periodic signal can be broken down into a summation of sine and cosine waves. A periodic time series $X(t)$ can be broken down into fundamental frequencies given by the Fourier Series:

$\begin{align} y(t) = \frac{a_0}{2}  + \sum_{n=1}^{\infty}{a_n cos(nt)) + b_n sin(nt)} \end{align}$

Integrating both sides by $\int+{-\pi}^{\pi} cos(mt)dt$ where $m$ is an integer:

$\begin{align} \int_{-\pi}^{\pi}{y(t)cos(mt)dt} = \frac{1}{2} a_0 + \sum_{n=1}^{\infty}{\int_{-\pi}^{\pi}a_n cos(nt)cos(mt)dt + \sum_{n=1}^{\infty}\int_{-\pi}^{\pi}b_n sin(nt) cos(mt)dt} \end{align}$

Via orthogonality between $cos$ and $sin$ over $[-\pi,\pi]$:

$\begin{align} \int_{-\pi}^{\pi}{cos(nt)cos(mt)dt} = 0,   m\neq n \end{align}$
$\begin{align} \int_{-\pi}^{\pi}{sin(nt)cos(mt)dt} = 0,   all m,n \end{align}$
$\begin{align} \int_{-\pi}^{\pi}{cos(nt)sin(mt)dt} = 0,   m\neq n \end{align}$

Equation 4 reduces to:

$\begin{align} \int_{-\pi}^{\pi}{y(t)cos(mt)dt} = a_m \int_{-\pi}^{\pi}cos^{2}(mt)dt \end{align}$

Solving for this a_m, we find that:

$\begin{align} a_m = \frac{1}{\pi}\int_{-\pi}^{\pi}{y(t) cos(mt)dt} \end{align}$

Doing the same for b_m, we find the similar coefficient:

$\begin{align} b_m = \frac{1}{\pi}\int_{-\pi}^{\pi}{y(t) sin(mt)dt} \end{align}$

for $n =0,1,2...,$ where:

$n = 0$ is the constant component of the signal

$n = 1$ is the \textit{fundamental} corresponding to a sine/cosine wave whose period matches $y(t)$'s period exactly

$n = 2$ is the first harmonic corresponding to a sine/cosine whose period matches $\frac{1}{2}$ of $y(t)$'s period

$n = 3$ is the second harmonic corresponding to a sine/cosine whose period matches $\frac{1}{3}$ of $y(t)$'s period

One useful quantity to look at is the energy of a signal as it tells us how "much" of a signal there is. We define a single coefficient $c_n^{2}$ to describe the "energy" of a signal at a specific frequency where $c_n$ is given by:

$\begin{align}c_n = \left[\frac{1}{2}(a^2_n + b^2_n)\right]^{\frac{1}{2}}\end{align}$

The square of these coefficients ($c_n^{2}$) corresponds to the energy of a certain frequency $n$.

## The Fourier Transform:

Transforming a signal from the time domain into the frequency ($\omega$) domain using this method is called the \textbf{Fourier Transform (FT)}. The FT is given by:

\begin{align} \hat{y}(\omega) = \int_{-\infty}^{\infty}{y(t)e^{-2\pi i \omega t}d\omega} \end{align}

To better understand the FT, let us take a look at some of its applications.

### FT of a Pure Signal

samp_rate = 500  # Sampling Frequency in Hertz
dur = 5  # Duration in Seconds
amp = 10  # Amplitude of Sine Wave
freq = 5  # Frequency of Sine Wave
x, y = sine_wave(amp, freq, samp_rate, dur, plot=True)

Let's work with ideal conditions first. Let's apply the Fourier Transform on the single sine wave with frequency of 5 Hz.

y_ft = scp.fft.fft(y)
freqs = scp.fft.fftfreq(x.shape[-1])*samp_rate
plt.subplots(figsize=(5, 5))
plt.plot(freqs, y_ft.real**2)
plt.ylabel('Energy')
plt.xlabel('Frequency (in Hz)')
print('Peak at:', np.argmax(y_ft.real**2)/dur, 'Hz')

We see two peaks when the frequency is 5 and -5.

The left-hand side corresponds to negative frequencies. Physically, negative frequencies are not very meaningful but their presence is important in calculating for the Fourier Transform. By finding where the energy peaks, we can find frequencies that best describe the time series.

Additionally, the FT is a perfectly (to numerical precision) reversible process. By knowing the energies of each frequency, we can retrieve the original signal by performing an \textbf{Inverse Fourier Transform (IFT)}.

plt.subplots(figsize=(15, 2))
plt.plot(x, scp.fft.ifft(y_ft).real)
plt.xlabel('Frequency')
print("Is close:", np.allclose(scp.fft.ifft(y_ft), y))

### FT on Mixed Signals

An important use of the FT is analyzing signals with mixed frequencies. We can perform the above exercise on 2 combined waves (or even an arbitrary number of waves with differing frequencies!) to find which frequencies are present in our signal.

samp_rate = 500  # Sampling Frequency in Hertz
dur = 5  # Duration in Seconds

amp1 = 1  # Amplitude of Sine Wave 1
freq1 = 5  # Frequency of Sine Wave 1

amp2 = 1  # Amplitude of Sine Wave 2
freq2 = 17  # Frequency of Sine Wave 2

x1, y1 = sine_wave(amp1, freq1, samp_rate, dur, plot=False)
x2, y2 = sine_wave(amp2, freq2, samp_rate, dur, plot=False)
y = y1 + y2  # Simply overlapping the two signals
plt.subplots(figsize=(15, 2))
plt.xlabel('Time')
plt.plot(x1, y);

Eyeballing the graph, it is challenging to determine its exact frequency make up.

y_ft = scp.fft.fft(y)
freqs = scp.fft.fftfreq(x1.shape[-1])*samp_rate

plt.subplots(figsize=(5, 5))
plt.plot(freqs, y_ft.real**2)
plt.xlabel('Frequency (in Hz)')
peaks = scp.signal.find_peaks(y_ft.real**2)
peak_vals = peaks[0][:len(peaks[0])//2]/dur
print('Peaks at:', peak_vals)

The FT makes it very easy to find the frequencies of the signal even if they are mixed.

## Noise Filtering

Real time series data are never free from noise making it tricky to find the real dynamics of whatever it is measuring. We may end up trying to model noise and end up overlooking the real behavior of the time series.

Thankfully, we can manipulate time series data in the frequency domain to eliminate one of the most types of noise - white noise. White noise pervades time series data and by definition, has components across all frequencies. Usually, a time series with white noise is represented as:

\begin{align} y(t) = s(t) + v(t) \end{align}

where $s(t)$ is the true signal we want to retrieve from $y(t)$ and $v(t)$ is white noise we want to eliminate. We simulate this below by adding white noise to the signal.

### Filtering by Energy Level

White noise is characterized by having energy levels almost equal for all frequencies. Such a signal can be generate by simply using a random number generator.

noise = np.random.uniform(-1,1, len(y))
plt.subplots(figsize=(15, 2))
plt.plot(x1, noise);

Taking the FT of the white noise, we find that indeed, for almost all the frequencies, there are positive energy values for them all. This means that for all frequencies, the white noise will have some contribution which can affect our analysis of the time series.

noise_ft = scp.fft.fft(noise)
freqs = scp.fft.fftfreq(x1.shape[-1])*samp_rate
peaks = scp.signal.find_peaks(noise_ft.real**2)

plt.subplots(figsize=(5, 5))
plt.plot(freqs, noise_ft.real**2)
plt.ylabel('Energy')
plt.xlabel('Frequency');

The addiiton of white noise to the mixed frequency signal makes its actual behavior less observable. The white noise has "corrupted" the signal and presents a challenge we find when making any measurement. How can we sift out the original mixed signal when the data we receive is noisy?

y_noisy = y+noise
plt.subplots(figsize=(15, 2))
plt.plot(x1, y_noisy);

The resulting FT of the noisy signal results in multiple peaks being found. This poses a problem if we are trying to find the appropriate periodic trends in our data. Luckily, the noise appears to be present only at low energy levels. Should measurements have been done reasonably well in collecting this data, noise can be managed as they only have low energy levels.

y_ft = scp.fft.fft(y_noisy)
freqs = scp.fft.fftfreq(x1.shape[-1])*samp_rate
peaks = scp.signal.find_peaks(y_ft.real**2)
peak_vals = peaks[0][:len(peaks[0])//2]/dur
print('Peaks at:', peak_vals)

plt.subplots(figsize=(5, 5))
plt.plot(freqs, y_ft.real**2)
plt.ylabel('Energy')
plt.xlabel('Frequency');

In crude fashion, we can set a hard threshold that removes the coefficients of frequencies below a certain energy level . One way to accomplish this is by setting the cut-off as a multiple of the standard deviation. The Fourier Transform allows us to make such

\textit{Conversely, we can perform the same method to eliminate high energy signals or even select those in a certain band}

threshold = 3*np.std(y_ft.real)
peaks = scp.signal.find_peaks(y_ft.real, threshold=threshold)
peak_vals = peaks[0][:len(peaks[0])//2]/dur
print('Peaks at:', peak_vals)

By visual inspection and setting an arbitrary threshold of 3x the standard deviation, we are able to retrieve a clearer signal of the dominant frequencies, albeit with the likely addiiton of some stray frequencies. Obtaining the exact signal from a noisy one can be very difficult but with judicious application and an understanding of the underlying systme, filtering by energy levels can be a quick and effective way to modify your time series.

y_ft_denoised = [0 if x < threshold else x for x in y_ft]
plt.subplots(figsize=(15, 2))
y_denoised = scp.fft.ifft(y_ft_denoised)
plt.plot(x1, y_denoised.real);

### Filtering by Frequency

hf_noise = sine_wave(amp, 70, samp_rate, dur, plot=False)[1]

y_hf_noisy = y+hf_noise
plt.subplots(figsize=(15, 2))
plt.plot(x, y_hf_noisy);

Observing the FT of a signal with high frequency noise, the noise dominates the energy spectrum and masks the underlying signal.

y_ft_hf = scp.fft.fft(y_hf_noisy)
freqs = scp.fft.fftfreq(x.shape[-1])*samp_rate
plt.plot(freqs, y_ft_hf.real**2)
peaks = scp.signal.find_peaks(y_ft_hf.real)
peak_vals = peaks[0][:len(peaks[0])//2]/dur
plt.ylabel('Energy')
plt.xlabel('Frequency')
print('Peaks at:', peak_vals)

While the loewr frequency peaks of the signal can still be retrieved, the extreme noise will make it untenable for most forecasting purposes. These high frequencies* can be eliminated to retrieve the original signal. Guided by our desire to observe the signals that exhibit lower frequencies, we can force the energies of all frequencies above a desired threshold to 0 and perform the IFT to obtain the

\textbf{*}\textit{We can do the same for low frequencies and those in a "band".}

freqs = scp.fft.fftfreq(x.shape[-1])*samp_rate
freqs

f_thresh = 20

freqs = scp.fft.fftfreq(x.shape[-1])*samp_rate
y_ft_hf_filtered = y_ft_hf.copy()
y_ft_hf_filtered[(abs(freqs.real) > f_thresh)] = 0

plt.plot(freqs, y_ft_hf_filtered.real**2)
plt.ylabel('Energy')
plt.xlabel('Frequency');
peaks = scp.signal.find_peaks(y_ft_hf_filtered.real)
peak_vals = peaks[0][:len(peaks[0])//2]/dur
print('Peaks at:', peak_vals)

plt.subplots(figsize=(15, 2))
y_lf = scp.fft.ifft(y_ft_hf_filtered).real
plt.plot(x1, y_lf.real);

### Example 3: Filtering via FT in the Jena Climate Dataset

Looking at climate information, we can glean insights into the seasonality of climate patterns. Let's examine the mean daily temperature of the Jena climate dataset and apply FT to examine its seasonality.

df = pd.read_csv('../data/jena_climate_2009_2016.csv')
display(df.head())
df['Date Time'] = pd.to_datetime(
    df['Date Time'], dayfirst=True, format="%d.%m.%Y %H:%M:%S")
df['Date'] = df['Date Time'].dt.date
mean_temps = df.groupby('Date')['T (degC)'].mean().values
plt.subplots(figsize=(15, 2))
plt.plot(temp)

There is a very strong trend that can be seen every year. Suppose we want to examine only this trend as we are interested in the general dynamics of the system. To highlight that aspect of the time series, we filter out frequencies corresponding to greater than 1 year by setting their energies to 0.

y_ft = scp.fft.fft(temp)
y_ft_copy = y_ft.copy()
freqs = scp.fft.fftfreq(len(temp), d=1/365)
y_ft_copy[(abs(freqs) >= 1)] = 0
plt.plot(freqs, abs(y_ft_copy))

By removing all frequencies greater than 1 year, we obtain only the lower frequency values. With these values, we can have a better look at the yearly dynamics of daily temperature without interference from inter-year dynamics. Should forecasts on actual values of the temperature need to be made, the value sof the frequencies removed can simply be added back.

y_hf = scp.fft.ifft(y_ft_copy)
plt.subplots(figsize=(15, 2))
# plt.title('Removing Yearly Cycle')
plt.plot(range(len(temp)), y_hf.real)

## Wavelet Transform

A wavelet transform (WT) allows you to measure how "much"of a certain type of wavelet there exists in a given signal. While both FT and WT both transform a time series into the frequency domain, a key difference is that WT gives you information about the time domain as well.

This transformation is achieved by imagining a specified wavelet sliding it across the entire signal through time. This wavelet that we scan is called the "mother wavelet". In addition to sliding the mother Wavelet across the span of the time series, its size, dependent on a scale factor, is varied as well while scanning. The scaling transformation applied on the mother Wavelet can be thought of as "stretching" it only without changing its frequency.

The wavelet transform of a time series $y(t)$ is given by:

$\begin{align} WT(y(t)) = \frac{1}{\sqrt{|\sigma|}}\int_{-\infty}^{\infty}y(t)\psi \left( \frac{t-h}{\sigma} \right)dt \end{align}$

where $\sigma$ is the scale, $h$ is the time lag/translation, and $\psi$ is the mother wavelet.

From the WT, we obtaing sets of coefficients corresponding to different time lags and scales. The greater the magnitude of the coefficients, the greater overlap there is between the scaled, time-shifted mother wavelet and the time series.

def rescale(arr, scale=2):
    n = len(arr)
    return np.interp(np.linspace(0, n, scale*n+1), np.arange(n), arr)

fig, ax = plt.subplots(figsize=(15, 2))
wav1 = pywt.ContinuousWavelet('gaus1')
int_psi1, x = pywt.integrate_wavelet(wav1)
for s in range(5):
    ax.plot(rescale(int_psi1, scale=s), label='Scale: '+ str(s))
    ax.legend()
    ax.set_title('Gaussian Mother Wavelet')

Suppose we have a signal of repearing Gaussian waves. Applying the WT on this signal using a mother wavelet corresponding to that of a Gaussian wavelet, we can pinpoint where the time series exhibits a Gaussian-like pattern.

s1 = list(scp.signal.gaussian(250, std=20))
s1 = np.tile(s1, 4)
plt.subplots(figsize=(15, 2))
plt.xlim(left=0, right=1000)
plt.ylabel('Value')
plt.xlabel('Time (t)')
plt.plot(s1)
plt.show()

coeffs, freqs = pywt.cwt(s1, range(1,250), wavelet='gaus1')
plt.subplots(figsize=(18.75, 2))
plt.xlim(left=0, right=1000)
plt.ylabel('Scale (s)')
plt.xlabel('Time (t)')
plt.imshow(coeffs, aspect='auto')
plt.colorbar();

By observing where the magnitudes of the WT are greatest, we can pinpoint the time and scale wherein there is Gaussian-like behavior in our time series. The WT is also useful in sifting out our signal from noise. We apply white noise once more to our Gaussian pulses and observe the WT's results.

s1 = list(scp.signal.gaussian(250, std=20))
noise = np.random.uniform(-1.5,1.5,4*len(s1))
s1 = np.tile(s1, 4) + noise
plt.subplots(figsize=(15, 2))
plt.xlim(left=0, right=1000)
plt.ylabel('Scale (s)')
plt.xlabel('Time (t)')
plt.plot(s1)
plt.show()

coeffs, freqs = pywt.cwt(s1, range(1,300), wavelet='gaus1')
plt.subplots(figsize=(18.75, 2))
plt.xlim(left=0, right=1000)
plt.ylabel('Scale (s)')
plt.xlabel('Time (t)')
plt.imshow(coeffs, aspect='auto')
plt.colorbar();

Regions of high magnitudes for our WT coefficients are still clearly visible albeit with minior interference from the white noise at smaller scales.

In general, choosing the mother wavelet depends on what kind of behavior you want or expect in your time series. Another popular mother wavelet used to identify sine waves in time series is the "Morlet" wavelet. This wavelet is a sine wave tapered by a Gaussian wave.

fig, ax = plt.subplots(figsize=(15, 2))
wav2 = pywt.ContinuousWavelet('morl')
int_psi2, x = pywt.integrate_wavelet(wav2)
for s in range(5):
    ax.plot(rescale(int_psi2, scale=s), label='Scale: '+ str(s))
    ax.set_title('Morlet Mother Wavelet')
    ax.legend()

Applying the Morlet wavelet on a mixed frequency sinusoidal signal, we can find that it is able to differentiate between the two frequencies as $s$ is varied.

sine5 = sine_wave(freq=5, duration =5, plot=False)[1]
sine10 = sine_wave(freq=10, plot=False)[1]
sine_5_10 = sine5+sine10
plt.subplots(figsize=(15, 2))
plt.plot(sine_5_10)
plt.show()
plt.subplots(figsize=(18.75, 2))
plt.xlim(left=0, right=1000)
plt.ylabel('Scale (s)')
plt.xlabel('Time (t)')
plt.imshow(coeffs, aspect='auto')
plt.colorbar();

### Example: 4 Using Accelerometer and Gyroscopic Data

The following code is taken from:

https://towardsdatascience.com/multiple-time-series-classification-by-using-continuous-wavelet-transformation-d29df97c0442

The following data contains time signals of accelerometer and gyroscopic data attached to people performing different activities. Throughout the duration of their activity, measurements were taken from the devices and recorded as time series. Each time series depicts a different activity.

def load_y_data(y_path):
    y = np.loadtxt(y_path, dtype=np.int32).reshape(-1, 1)
    # change labels range from 1-6 t 0-5, this enables a sparse_categorical_crossentropy loss function
    return y - 1

def load_X_data(X_path):
    X_signal_paths = [X_path + file for file in os.listdir(X_path)]
    X_signals = [np.loadtxt(path, dtype=np.float32) for path in X_signal_paths]
    return np.transpose(np.array(X_signals), (1, 2, 0))

PATH = '../data/cwt/'
LABEL_NAMES = ["Walking", "Walking upstairs",
               "Walking downstairs", "Sitting", "Standing", "Laying"]

# load X data
X_train = load_X_data(PATH + 'train/Inertial Signals/')
X_test = load_X_data(PATH + 'test/Inertial Signals/')
# load y label
y_train = load_y_data(PATH + 'train/y_train.txt')
y_test = load_y_data(PATH + 'test/y_test.txt')

Quite evidently, we find that walking has a different "look" from that of simply laying down. Humans can easily determine this at a glance but can be difficult for machines to differentiate between the two signals. The WT allows for the creation of images that contain temporal and frequency information providing a compact and meaningful representation of these time series.

fig, ax = plt.subplots(ncols=2, figsize=(15, 5))
pd.DataFrame(X_train[79]).plot(ax=ax[0], title='Walking')
pd.DataFrame(X_train[51]).plot(ax=ax[1], title='Laying');

def split_indices_per_label(y):
    indicies_per_label = [[] for x in range(0, 6)]
    # loop over the six labels
    for i in range(6):
        indicies_per_label[i] = np.where(y == i)[0]
    return indicies_per_label

def plot_cwt_coeffs_per_label(X, label_indicies, label_names, signal, sample, scales, wavelet):

    fig, axs = plt.subplots(nrows=2, ncols=3, sharex=True,
                            sharey=True, figsize=(12, 5))

    for ax, indices, name in zip(axs.flat, label_indicies, label_names):
        coeffs, freqs = pywt.cwt(
            X[indices[sample], :, signal], scales, wavelet=wavelet)
        ax.imshow(coeffs, cmap='coolwarm', aspect='auto')
        ax.set_title(name)
        ax.spines['right'].set_visible(False)
        ax.spines['top'].set_visible(False)
        ax.set_ylabel('Scale')
        ax.set_xlabel('Time')
    plt.tight_layout()

train_labels_indicies = split_indices_per_label(y_train)

# signal indicies: 0 = body acc x, 1 = body acc y, 2 = body acc z, 3 = body gyro x, 4 = body gyro y, 5 = body gyro z, 6 = total acc x, 7 = total acc y, 8 = total acc z
signal = 3  # signal index
sample = 1  # sample index of each label indicies list
scales = np.arange(1, 65)  # range of scales
wavelet = 'morl'  # mother wavelet

plot_cwt_coeffs_per_label(X_train, train_labels_indicies, LABEL_NAMES, signal,
                          sample, scales, wavelet)

for sample in range(2, 7):
    plot_cwt_coeffs_per_label(X_train, train_labels_indicies, LABEL_NAMES,
                              signal, sample, scales, wavelet)

Looking at the plots for each activity, we see can see some patterns between the images. By feeding these results into a convolutional neural network, we can create a classifier to classify time series into activities.

## Summary

To summarize, using these techniques can extract valuable information about time series which are not readily available.

Cross-correlations can give insights into the sychronicity of data. With careful analysis, these can even be used as a step towards a more holistic view of causality in conjunction with Granger Causality and Convergent Cross Mapping.

Fourier Transformation allows us to view the time series in the frequency domain, allowing operations previously untenable in the time domain. Filtering is a common task where FT is useful as it lets us easily manipulate the data to remove noise or highlight aspects of interest within the time series.

Wavelet Transformation lets us decompose time series more finely than the FT since we can define the "mother" wavelet to be our wavefunction of interest. By characterizing time series, we can quite literally, paint a picture of the time series that can be used to pick out specific waveforms in the time series.

In the context of forecasting, all three techniques can be used to inform the modeler how to prepare their data. With cross-correlations, the best time lagged time series can be used to provide better information about the target. The Fourier Transform can be applied to denoise the data and remove certain trends. Wavelet Transforms can be used to classify time series allowing the modeler to include their classification as a feature for forecasting or creating separate models for each class of time series. Again, these techniques serve as complements to predictive methods and can be used as feature engineering techniques to improve time series forecasing.

The following chapter represents the culmination of all the forecasting concepts from previous sections. There is no better way to test your time series capabilities than with a competition. The next chapter discusses the best techniques which were utilized in the recently concluded M5 Competition. In it, the best models under some constraints, are pushed to the limit to perform time series forecasting.

## References

The contents of this notebook are complied from the following references:

 *  Chapters 1-3 of Priestley, M.B. (1981). Spectral Analysis and Time Series, Vols. 1 and 2, Academic Press, New York.

 * <a href='https://towardsdatascience.com/multiple-time-series-classification-by-using-continuous-wavelet-transformation-d29df97c0442'>Multiple Time Series Classification by Using Continuous Wavelet Transformation</a>

 * <a href='https://pywavelets.readthedocs.io/en/latest/'>PyWavelets Documentation</a>

 * <a href='https://www.mathworks.com/help/wavelet/gs/interpreting-continuous-wavelet-coefficients.html'>Interpreting Continuous Wavelet Coefficients</a>

 *
Ngui, W. K., Leong, M. S., Hee, L. M., & Abdelrhman, A. M. (2013). Wavelet Analysis: Mother Wavelet Selection Methods. Applied Mechanics and Materials, 393, 953–958. https://doi.org/10.4028/www.scientific.net/amm.393.953

import numpy,import scipy,from statsmodels


# packages, rich is terminal output formatting
import numpy as np
import math
import rich
from rich import print, pretty
import matplotlib.pyplot as plt
pretty.install()

# GMB Model
# log(x_T) ~ N(log(x_S)+(mu-sigma^2/2)(T-S),sigma^2(T-S))

def SimulateGBM(S0, r, sd, T, paths, steps, reduce_variance = True):
    steps = int(steps)
    dt = T/steps
    Z = np.random.normal(0, 1, paths//2 * steps).reshape((paths//2, steps))
    # Z_inv = np.random.normal(0, 1, paths//2 * steps).reshape((paths//2, steps))
    if reduce_variance:
      Z_inv = -Z
    else:
      Z_inv = np.random.normal(0, 1, paths//2 * steps).reshape((paths//2, steps))
    dWt = math.sqrt(dt) * Z
    dWt_inv = math.sqrt(dt) * Z_inv
    dWt = np.concatenate((dWt, dWt_inv), axis=0)
    St = np.zeros((paths, steps + 1))
    St[:, 0] = S0
    for i in range (1, steps + 1):
        St[:, i] = St[:, i - 1]*np.exp((r - 1/2*np.power(sd, 2))*dt + sd*dWt[:, i-1])

    return St[:,1:]
    #return St

######## Test ##############################
S0_value = 36
r_value = 0.06
sd_value = 0.2
T_value = 1
paths_value = 100
steps_value = 50

Test_GBM = SimulateGBM(S0=S0_value, r=r_value, sd=sd_value, T=T_value, paths=paths_value,
steps=steps_value)

########## Plot the Data ################

Time_steps = np.arange(1, steps_value+1)

#plt.plot(Time_steps, Test_GBM.T)
#plt.show()

######################################################################


from scipy.spatial.distance import pdist, squareform
from scipy import exp
from scipy.linalg import eigh
import numpy as np

# Implementing a kernel principal component analysis in Python

def rbf_kernel_pca1(X, gamma, n_components):
    """
    RBF kernel PCA implementation.

    Parameters
    ------------
    X: {NumPy ndarray}, shape = [n_examples, n_features]

    gamma: float
      Tuning parameter of the RBF kernel

    n_components: int
      Number of principal components to return

    Returns
    ------------
     X_pc: {NumPy ndarray}, shape = [n_examples, k_features]
       Projected dataset

    """
    # Calculate pairwise squared Euclidean distances
    # in the MxN dimensional dataset.
    sq_dists = pdist(X, 'sqeuclidean')

    # Convert pairwise distances into a square matrix.
    mat_sq_dists = squareform(sq_dists)

    # Compute the symmetric kernel matrix.
    K = exp(-gamma * mat_sq_dists)

    # Center the kernel matrix.
    N = K.shape[0]
    one_n = np.ones((N, N)) / N
    K = K - one_n.dot(K) - K.dot(one_n) + one_n.dot(K).dot(one_n)

    # Obtaining eigenpairs from the centered kernel matrix
    # scipy.linalg.eigh returns them in ascending order
    eigvals, eigvecs = eigh(K)
    eigvals, eigvecs = eigvals[::-1], eigvecs[:, ::-1]

    # Collect the top k eigenvectors (projected examples)
    X_pc = np.column_stack([eigvecs[:, i]
                            for i in range(n_components)])

    return X_pc

def rbf_kernel_pca2(X, gamma, n_components):
    """
    RBF kernel PCA implementation.

    Parameters
    ------------
    X: {NumPy ndarray}, shape = [n_examples, n_features]

    gamma: float
      Tuning parameter of the RBF kernel

    n_components: int
      Number of principal components to return

    Returns
    ------------
     alphas: {NumPy ndarray}, shape = [n_examples, k_features]
       Projected dataset

     lambdas: list
       Eigenvalues

    """
    # Calculate pairwise squared Euclidean distances
    # in the MxN dimensional dataset.
    sq_dists = pdist(X, 'sqeuclidean')

    # Convert pairwise distances into a square matrix.
    mat_sq_dists = squareform(sq_dists)

    # Compute the symmetric kernel matrix.
    K = exp(-gamma * mat_sq_dists)

    # Center the kernel matrix.
    N = K.shape[0]
    one_n = np.ones((N, N)) / N
    K = K - one_n.dot(K) - K.dot(one_n) + one_n.dot(K).dot(one_n)

    # Obtaining eigenpairs from the centered kernel matrix
    # scipy.linalg.eigh returns them in ascending order
    eigvals, eigvecs = eigh(K)
    eigvals, eigvecs = eigvals[::-1], eigvecs[:, ::-1]

    # Collect the top k eigenvectors (projected examples)
    alphas = np.column_stack([eigvecs[:, i]
                              for i in range(n_components)])

    # Collect the corresponding eigenvalues
    lambdas = [eigvals[i] for i in range(n_components)]

    return alphas, lambdas


from basic_operations import Basic_Operations
import numpy as np

class Operations_One_Matrix(Basic_Operations):
    """This class performs basic operations with a matrix
    """

    def __init__(self, matrix1=None, matrix2=None):
        super().__init__()
        self.matrix1 = self.check_type_matrix(matrix1)
        self.matrix2 = self.check_type_matrix(matrix2)

    def scalar(self, number):
        """scalar : multiply a matrix by a scalar

        Args:
            number: number to multiply by a matrix

        Return : result of the operation between matrices of type <<numpy.ndarray>>.
                If an error occurs, it returns a message indicating what the error is.
        """
        result = self.matrix1 * number
        return result

    def determinant(self):
        """determinant:  calculates the determinant of a matrix
        Return : an integer
        """
        determinat = np.linalg.det(self.matrix1)
        return determinat

    def shape(self):
        """shape: checks the dimensions of a matrix

        Return: Tuple of array dimensions.
        """
        dimensions = self.matrix1.shape
        return dimensions

    def inverse(self):
        """inverse : calculate the inverse of a matrix

        Return : result of the operation between matrices of type <<numpy.ndarray>>.
                If an error occurs, it returns a message indicating what the error is.
        """
        try:
            if self.determinant() != 0 and (self.shape()[0] == self.shape()[1]):
                inverse = np.linalg.inv(self.matrix1)
            else:
                raise Exception

        except Exception as e:
            return f"Error: {e}"
        return inverse

    def transpose(self):
        """transpose: Invert or permute the axes of a matrix
        Return: returns the modified array with the matrix transpose
        """
        try:
            transpose = np.transpose(self.matrix1)
            return transpose

        except Exception as e:
            return f"Error: {e}"