# Example 5.3: Estimating Spot and Roll Returns Using the Constant Returns Model
import numpy as np
import pandas as pd
# import matplotlib.pyplot as plt
import statsmodels.api as sm

# import statsmodels.tsa.stattools as ts
# import statsmodels.tsa.vector_ar.vecm as vm

df = pd.read_csv('inputDataDaily_C2_20120813.csv')
df['Date'] = pd.to_datetime(df['Date'], format='%Y%m%d').dt.date  # remove HH:MM:SS
df.set_index('Date', inplace=True)

# Find spot prices
spot = df['C_Spot']
df.drop('C_Spot', axis=1, inplace=True)

T = sm.add_constant(range(spot.shape[0]))

model = sm.OLS(np.log(spot), T)
res = model.fit()  # Note this can deal with NaN in top row

print('Average annualized spot return=', 252 * res.params[1])
# Average annualized spot return= 0.02805562210100287

# Fitting gamma to forward curve
gamma = np.full(df.shape[0], np.nan)
for t in range(df.shape[0]):
    idx = np.where(np.isfinite(df.iloc[t, :]))[0]
    idxDiff = np.roll(idx, -1) - idx
    all_ones = all(idxDiff[0:4] == 1)

    if (len(idx) >= 5) and all_ones:
        FT = df.iloc[t, idx[:5]]
        T = sm.add_constant(range(FT.shape[0]))
        model = sm.OLS(np.log(FT.values), T)
        res = model.fit()
        gamma[t] = -12 * res.params[1]

pd.DataFrame(gamma).plot()

print('Average annualized roll return=', np.nanmean(gamma))
# Average annualized roll return= -0.12775650227459556


import math
from .utils import get_activities_over_time_as_list
from .state import State
import numpy as np

def shannon_entropy(string):
    """
    Calculates the Shannon entropy for the given string.

    :param string: any string, such as '000101001', '12402', or 'aBcd1234ef5g'

    :return: a real number representing the Shannon entropy
    """
    symbols = dict.fromkeys(list(string))
    symbol_probabilities = [float(string.count(symbol)) / len(string) for symbol in symbols]
    H = -sum([p_symbol * math.log(p_symbol, 2.0) for p_symbol in symbol_probabilities])
    return H + 0  # add 0 as a workaround so we don't end up with -0.0

def average_node_entropy(trajectory_or_activities):
    """
    Calculates the average node entropy in the given automaton evolution, where entropy is the Shannon entropy.
    The state of a node over time is represented as a string, and its entropy is calculated. The same is done for all
    nodes in this Network Automaton, and the average entropy is returned.

    :param trajectory_or_activities: a list of States that represents the evolution of the automaton, or a list of
                                     lists that represents the evolution of the automaton, where an inner list
                                     represents the activities of the nodes at a given timestep

    :return: a real number representing the average node Shannon entropy
    """
    if len(trajectory_or_activities) is 0:
        raise Exception("there are no activities")
    if isinstance(trajectory_or_activities[0], State):
        # trajectory_or_activities is a trajectory, convert it to an activities list
        activities = get_activities_over_time_as_list(trajectory_or_activities)
    else:
        activities = trajectory_or_activities
    num_cols = len(activities[0])
    entropies = []
    for i in range(0, num_cols):
        node_states_over_time = ''.join([str(x) for x in [y[i] for y in activities]])
        entropy = shannon_entropy(node_states_over_time)
        entropies.append(entropy)
    return np.mean(entropies)

def joint_shannon_entropy(stringX, stringY):
    """
    Calculates the joint Shannon entropy between the given strings, which must be of the same length.

    :param stringX: any string, such as '000101001', '12402', or 'aBcd1234ef5g'

    :param stringY: any string, such as '000101001', '12402', or 'aBcd1234ef5g'

    :return: a real number representing the joint Shannon entropy between the given strings
    """
    X = np.array(list(stringX))
    Y = np.array(list(stringY))
    joint_symbol_probabilities = []
    for x in set(X):
        for y in set(Y):
            joint_symbol_probabilities.append(np.mean(np.logical_and(X == x, Y == y)))
    return sum(-p * np.log2(p) for p in joint_symbol_probabilities if p != 0)

def mutual_information(stringX, stringY):
    """
    Calculates the mutual information between the given strings, which must be of the same length.

    :param stringX: any string, such as '000101001', '12402', or 'aBcd1234ef5g'

    :param stringY: any string, such as '000101001', '12402', or 'aBcd1234ef5g'

    :return: a real number representing the mutual information between the given strings
    """
    return shannon_entropy(stringX) + shannon_entropy(stringY) - joint_shannon_entropy(stringX, stringY)

def average_mutual_information(trajectory_or_activities, temporal_distance=1):
    """
    Calculates the average mutual information between a node and itself at the next n time steps, given by the
    specified temporal distance. A temporal distance of 1 means the next time step.
    For example, consider the following string, '00101010110', which represents the state of a node over 11 time steps.
    The strings which will be used for the computation of the mutual information between a node and itself at the
    next time step are: '0010101011' and '0101010110', since we pair each time-step value with its next value:
    " 00101010110"
    "00101010110 "

    :param trajectory_or_activities: a list of States that represents the evolution of the automaton, or a list of
                                     lists that represents the evolution of the automaton, where an inner list
                                     represents the activities of the nodes at a given timestep

    :param temporal_distance: the size of temporal separation, where the value must be greater than 0 and
                              less than the number of time steps.

    :return: a real number representing the average mutual information between a node and itself at the next time step
    """
    if len(trajectory_or_activities) is 0:
        raise Exception("there are no activities")
    if isinstance(trajectory_or_activities[0], State):
        # trajectory_or_activities is a trajectory, convert it to an activities list
        activities = get_activities_over_time_as_list(trajectory_or_activities)
    else:
        activities = trajectory_or_activities
    num_cols = len(activities[0])
    if not (0 < temporal_distance < num_cols):
        raise Exception("the temporal distance must be greater than 0 and less than the number of time steps")
    mutual_informations = []
    for i in range(0, num_cols):
        node_states_over_time = ''.join([str(x) for x in [y[i] for y in activities]])
        mi = mutual_information(node_states_over_time[:-temporal_distance], node_states_over_time[temporal_distance:])
        mutual_informations.append(mi)
    return np.mean(mutual_informations)


import numpy as np

# how MSE works:
# Takes each predictions deviance from the actual value, squares it, then
# averages the squared values. The Gauss-Markov theorem guarantees that the
# solution to linear regression is the best in the sense that the
# coefficients have the smallest expected squared error.
def MSE(target, predictions):
    squared_deviation = np.power(target - predictions, 2)
    return np.mean(squared_deviation)

# How MAD works:
# MAD is similar to MSE in that it averages to get the solution. The only
# differnce is that MAD is the absolute value of the difference between
# the prediction and the actual value.
def MAD(target, predictions):
    absolute_deviation = np.abs(target - predictions)
    return np.mean(absolute_deviation)


# https://www.hackerrank.com/challenges/np-transpose-and-flatten/problem

import numpy

len_row, len_column = [int(s) for s in input().split()]
# 4 3

arr_l = []
for _ in range(len_row):
    arr_l.append([int(s) for s in input().split()])
# 1 2 3 4
# 5 6 7 8
# 9 10 11 12
# 13 14 15 16

arr = numpy.array(arr_l)
# print(arr)
# [[ 1  2  3  4]
#  [ 5  6  7  8]
#  [ 9 10 11 12]
#  [13 14 15 16]]

print(arr.transpose())
# [[ 1  5  9 13]
#  [ 2  6 10 14]
#  [ 3  7 11 15]
#  [ 4  8 12 16]]

print(arr.flatten())
# [ 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16]

# print(arr)
# [[ 1  2  3  4]
#  [ 5  6  7  8]
#  [ 9 10 11 12]
#  [13 14 15 16]]

__Training__/Python - HackerRank/16. Numpy/Transpose and Flatten.py


## Welcome to Pytorch

## Import convention seems to be just torch as opposed to how numpy and pandas set to np/pd.

import torch
import numpy as np
import pandas as pd

## torch is all about tensors. recall that a tensor is a type of matrix
# https://medium.com/@quantumsteinke/whats-the-difference-between-a-matrix-and-a-tensor-4505fbdc576c

a = torch.Tensor([1,2,3,4,5])
print(a.dtype)
print(a.type())
print(a.size())
print(a.ndimension())

a_col=a.view(5,1)

print(a_col)

a_col=a.view(-1,1)

print(a_col)

a = torch.Tensor([0,1,2,3,4,5])
a_col=a.view(6,1)
print(a_col)

numpy_array=np.array([0.0,1.0,2.0,3.0,4.0])
torch_tensor=torch.from_numpy(numpy_array)
back_to_numpy=torch_tensor.numpy()

print(numpy_array,torch_tensor,back_to_numpy)

pandas_series=pd.Series([0.1,2,0.3,10.1])
pandas_to_torch=torch.from_numpy(pandas_series.values)

print(pandas_series,pandas_to_torch)

a = torch.Tensor([0,1,2,3,4,5])
a_list = a.tolist()
print(a_list)

a = torch.Tensor([0,1,2,3,4,5])

print(a[0]) #prints a tensor
print(a[0].item()) # prints the number

c = torch.Tensor([10,0.5,20,3])
print(c)
c[0]=100
print(c)
c[3]=10
print(c)
d=c[1:3]
print(d)
d[0]=0
print(d)

print(c)
c[1:4]=torch.tensor([1,2,3])
print(c)

#vector arithmetic

u = torch.tensor([1,0])
v = torch.tensor([0,1])
z = u + v
print(z)

y = torch.tensor([1,2])
z = 2*y
print(z)

u = torch.tensor([1, 2, 3, -1])
v = u + 1
print(v)

u = torch.tensor([1, 2])
v = torch.tensor([3, 2])
z = u*v
print(z)

# dot product

u = torch.tensor([1,2])
v = torch.tensor([3,1])
z = torch.dot(u,v)
print(z)

# the act of adding a scalar to a tensor is called "broadcasting"
u = torch.tensor([1,2,3,-1])
z = u+1
print(z)

a = torch.tensor([1.0,-1.0,1.0,-1.0])
mean_a = a.mean()
print(mean_a)

b = torch.tensor([1,-2,3,4,5])
print(b.max())

pi = np.pi
x = torch.tensor([0, pi/2, pi])
y = torch.sin(x)
print(y)

#return evenly spaced numbers over a specified interval
#this is pretty neat.
z = torch.linspace(-2,2, steps=5)
print(z)

x=torch.linspace(0,2*pi,100)
y=torch.sin(x)

import matplotlib.pyplot as plt
plt.plot(x.numpy(),y.numpy())


import numpy as np

def log_likelihood_joint(points, pi, mu, sigma, **kwargs):
        """
        Args:
            points: NxD numpy array, the observations
            pi: np array of length K, the prior of each component
            mu: KxD numpy array, the center for each gaussian.
            sigma: KxD numpy array, the diagonal standard deviation of each gaussian.
        Return:
            ll(log-likelihood): NxK array, where ll(i, j) = log pi(j) + log NormalPDF(points_i | mu[j], sigma[j])

        Hint: Assume that the three dimensions of our multivariate gaussian are independent.
              This allows you to write treat it as a product of univariate gaussians.
        """
        lls = np.zeros(shape = (points.shape[0],np.size(pi)))
        for j in range(np.size(pi)):
            lls[:,j] = np.log(pi[j] + 1e-12)
            temp = points - mu[j,:]
            #cov = sigma[j,:]**(-1)
            x = 1
            for i in range(points.shape[1]):
                x *= 1/(np.sqrt(2 * np.pi) * np.sqrt(sigma[j, i]))
                y = -1/(2*(sigma[j, i]))
                x *= np.exp(y * (temp[:,i]**2))
            #(x-mu).transpose*inverse(covariance matrix)*(x-mu)
            #1xD*D*K
            #x = np.exp(-1/2 * np.sum(np.dot(temp, np.diag(cov)) * temp, axis = 1))

            lls[:,j] += np.log(x + 1e-12)
        return lls


import numpy as np
# import matplotlib.pyplot as plt

CLASSIFICATION = 0
REGRESSION     = 1

def generate_dataset(size=1000, nfeatures=2,
        nclasses=2, task=CLASSIFICATION):

    np.random.seed(420)
    X = np.random.randn(size, nfeatures)

    X = np.concatenate(
        (
            np.ones((size, 1)), X
        ),
        axis=1
    )

    thetas = np.random.randn(nfeatures+1, 1)

    labels = X.dot(thetas)[:, 0]*10

    if task == REGRESSION:
        return X, labels

    # Otherwise return the classification splitted at the mean
    if nclasses == 2:
        splitter = labels.mean()

        labels[labels >= splitter] = 1
        labels[labels < splitter]  = 0

        return X, labels

    if nclasses == 3:
        m, sigma = labels.mean(), 3
        splitter1, splitter2 = m-sigma, m+sigma

        labels[labels<splitter1] = 0
        for i in range(len(labels)):
            if labels[i] >=splitter1 and labels[i]<splitter2:
                labels[i] = 1
        labels[labels>splitter2] = 2

        print(np.unique(labels))
        return X, labels

def plot_dataset(X, y, **options):
    import matplotlib.pyplot as plt

    plt.style.use('ggplot')
    plt.scatter(X[:, 1], X[:, 2], c=y, cmap='plasma', edgecolor='k', **options)
    plt.show()

if __name__=='__main__':
    import matplotlib.pyplot as plt

    X, labels =  generate_dataset(size=100, nfeatures=2, task=CLASSIFICATION)
    plt.scatter(X[:, 1], X[:, 2], c=labels, cmap='plasma')
    plt.show()


import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from sklearn.cluster import KMeans
from sklearn.datasets import make_blobs

def simulate_data(n = 500, features = 10, centroids = 3):
    '''
    Simulates n data points, each with number of features equal to features, with a number of centers equal to centroids
    INPUT (defaults)
        n = number of rows (500)
        features = number of columns (10)
        centroids = number of centers (3)
    Output
        dataset = a dataset with the the specified characteristics
    '''
    dataset, y = make_blobs(n_samples=n, n_features=features, centers=centroids, random_state=42)

    return dataset

def plot_data(data, labels):
    '''
    Plot data with colors associated with labels
    '''
    fig = plt.figure();
    ax = Axes3D(fig)
    ax.scatter(data[:, 0], data[:, 1], data[:, 2], c=labels, cmap='tab10');

data = simulate_data(200, 5, 4)

def get_kmeans_score(data, center):
    '''
    returns the kmeans score regarding SSE for points to centers
    INPUT:
        data - the dataset you want to fit kmeans to
        center - the number of centers you want (the k value)
    OUTPUT:
        score - the SSE score for the kmeans model fit to the data
    '''
    #instantiate kmeans
    kmeans = KMeans(n_clusters=center)

    # Then fit the model to your data using the fit method
    model = kmeans.fit(data)

    # Obtain a score related to the model fit
    score = np.abs(model.score(data))

    return score

def fit_mods():
    scores = []
    centers = list(range(1,11))

    for center in centers:
        scores.append(get_kmeans_score(data, center))

    return centers, scores


import numpy as np

class GreedyKCenter(object):
    def fit(self, points, k):
        centers = []
        centers_index = []
        # Initialize distances
        distances = [np.inf for u in points]
        # Initialize cluster labels
        labels = [np.inf for u in points]

        for cluster in range(k):
            # Let u be the point of P such that d[u] is maximum
            u_index = distances.index(max(distances))
            u = points[u_index]
            # u is the next cluster center
            centers.append(u)
            centers_index.append(u_index)

            # Update distance to nearest center
            for i, v in enumerate(points):
                distance_to_u = self.distance(u, v)  # Calculate from v to u
                if distance_to_u < distances[i]:
                    distances[i] = distance_to_u
                    labels[i] = cluster

            # Update the bottleneck distance
            max_distance = max(distances)

        # Return centers, labels, max delta, labels
        self.centers = centers
        self.centers_index = centers_index
        self.max_distance = max_distance
        self.labels = labels

    @staticmethod
    def distance(u, v):
        displacement = u - v
        return np.sqrt(displacement.dot(displacement))


#!/usr/bin/python

from numpy import *

#	Input
#	X is the data matrix itself
#	rank is the expected number of eigen values
#	num_of_random_column is the number of random vectors to project down to

def random_svd(X, rank, num_of_random_column):

	if rank + num_of_random_column > X.shape[1]:
		num_of_random_column = X.shape[1] - rank

	random_matrix = random.normal(size=(X.shape[1], num_of_random_column))
	omega, r = linalg.qr(random_matrix, mode='reduced')
	X_hat = X.dot(X.T)

	Q, R = linalg.qr(X_hat.dot(X_hat).dot(X).dot(omega), mode='reduced')
	smaller_matrix = Q.T.dot(X)
	U,S,V = linalg.svd(smaller_matrix)
	U = Q.dot(U)

	return U,S,V

if __name__ == '__main__':
	#	print setting
	set_printoptions(suppress=True)
	set_printoptions(precision=3)

	#	program settings
	desired_rank = 5
	num_of_random_column = 10
	example_size = 20

	#	-------------------------
	X = random.normal(size=(example_size, example_size))
	U,Se,V1 = random_svd(X, desired_rank, num_of_random_column)
	U1 = U[0:10, 0:desired_rank]

	U,S,V2 = linalg.svd(X)
	U2 = U[0:10, 0:desired_rank]

	print 'Estimated Eigen Values : \n' , Se[0:desired_rank]
	print 'Real Eigen Values : \n' , S[0:desired_rank] , '\n'
	print 'Estimated U : '
	print U1 , '\n'
	print 'Real U : '
	print U2 , '\n'
	print 'Estimated V : '
	print V1 , '\n'
	print 'Real V : '
	print V2 , '\n'


"""
https://www.statology.org/plot-normal-distribution-python/
"""
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import norm

# x-axis ranges from -5 and 5 with .001 steps
x = np.arange(-5, 5, 0.001)

# define multiple normal distributions
plt.plot(x, norm.pdf(x, 0, 1), label='μ: 0, σ: 1', color='gold')
plt.plot(x, norm.pdf(x, 0, 1.5), label='μ:0, σ: 1.5', color='red')
plt.plot(x, norm.pdf(x, 0, 2), label='μ:0, σ: 2', color='pink')

# add legend to plot
plt.legend(title='Parameters')

# add axes labels and a title
plt.ylabel('Density')
plt.xlabel('x')
plt.title('Normal Distributions', fontsize=14)
plt.show()
plt.savefig("normal_distribution.png")


"""
Five number summary example
"""
import numpy as np
# Students who has scored in Tamil Language
tamilMarks = np.array([82,74,56,35,89,78,90,100,84,65,54,59,45])
summaryTitles = ["Min","Lower Quartile", "Median", "Upper Quartile", "Maximum"]
tukey = [np.min(tamilMarks),
          np.percentile(tamilMarks, 25, interpolation='midpoint'),
          np.median(tamilMarks),
          np.percentile(tamilMarks, 75, interpolation='midpoint'),
          np.max(tamilMarks)]
for index, v in enumerate(tukey):
    print(summaryTitles[index], ':',v)


import numpy as np

def as_array(data):
    if np.isscalar(data):
        return np.array(data)

class Variable:
    def __init__(self, data):
        if not isinstance(data, np.ndarray):
            raise TypeError(f"{type(data)} type is not supported")
        self.data = data
        self.grad = None
        self.creator = None

    def set_creator(self, creator_func):
        self.creator = creator_func

    def backward(self):
        if self.grad is None:
            self.grad = np.ones_like(self.data)
        funcs = [self.creator]
        while funcs:
            f = funcs.pop()
            x, y = f.input, f.output
            x.grad = f.backward(y.grad)
            if x.creator is not None:
                funcs.append(x.creator)

class Function:
    def __call__(self, x: Variable):
        y = self.forward(x.data)
        output = Variable(as_array(y))
        output.set_creator(self)
        self.input = x
        self.output = output
        return output

    def forward(self, x_data):
        raise NotImplementedError()

    def backward(self, dout):
        raise NotImplementedError()

class Square(Function):
    def forward(self, x_data):
        return x_data ** 2

    def backward(self, dout):
        x_data = self.input.data
        return dout * 2 * x_data

class Exp(Function):
    def forward(self, x_data):
        return np.exp(x_data)

    def backward(self, dout):
        x_data = self.input.data
        return dout * np.exp(x_data)

def square(x):
    return Square()(x)

def exp(x):
    return Exp()(x)

if __name__ == "__main__":
    x = Variable(np.array(0.5))
    y = square(exp(square(x)))
    y.backward()
    print(f"x.grad is : {x.grad}")


# coding=utf-8
import numpy as np
from scipy import stats
import matplotlib.pyplot as plt

# 构造训练数据
x = np.arange(0., 10., 0.2)
m = len(x)  # 训练数据点数目
x0 = np.full(m, 1.0)
input_data = np.vstack([x0, x]).T  # 将偏置b作为权向量的第一个分量
target_data = 2 * x + 5 + np.random.randn(m)

# 两种终止条件
loop_max = 10000  # 最大迭代次数(防止死循环)
epsilon = 1e-3

# 初始化权值
np.random.seed(0)
w = np.random.randn(2)
# w = np.zeros(2)

alpha = 0.001  # 步长(注意取值过大会导致振荡,过小收敛速度变慢)
diff = 0.
error = np.zeros(2)
count = 0  # 循环次数
finish = 0  # 终止标志
error_list = []

# -------------------------------------------随机梯度下降算法----------------------------------------------------------
while count < loop_max:
    count += 1

    # 遍历训练数据集，不断更新权值
    for i in range(m):
        diff = np.dot(w, input_data[i]) - target_data[i]  # 训练集代入,计算误差值

        # 采用随机梯度下降算法,更新一次权值只使用一组训练数据
        w = w - alpha * diff * input_data[i]
        sum_m = np.sum(diff * input_data[i])
        error_list.append(sum_m**2)
        # ------------------------------终止条件判断-----------------------------------------
        # 若没终止，则继续读取样本进行处理，如果所有样本都读取完毕了,则循环重新从头开始读取样本进行处理。

    # ----------------------------------终止条件判断-----------------------------------------
    # 注意：有多种迭代终止条件，和判断语句的位置。终止判断可以放在权值向量更新一次后,也可以放在更新m次后。
    if np.linalg.norm(w - error) < epsilon:     # 终止条件：前后两次计算出的权向量的绝对误差充分小
        finish = 1
        break
    else:
        error = w
print 'loop count = %d' % count,  '\tw:[%f, %f]' % (w[0], w[1])

# ----------------------------------------------------------------------------------------------------------------------

# check with scipy linear regression
slope, intercept, r_value, p_value, slope_std_error = stats.linregress(x, target_data)
print 'intercept = %s slope = %s' % (intercept, slope)

plt.plot(range(len(error_list[0:100])), error_list[0:100])
plt.show()

plt.plot(x, target_data, 'k+')
plt.plot(x, w[1] * x + w[0], 'r')
plt.show()


import matplotlib.pyplot as plt
import numpy as np

t = np.arange(0., 5., 0.2)       # (start, stop, step), i.e. 0,0.2,0.4 ....4.8 excluding 5
'''
plot(*args, **kwargs)
args is a variable length argument, allowing for multiple x, y pairs with an optional format string
i.e. (x, y, [format]), (x, y, [format])
1) if only one given, it's y, i.e. (y) is valid, or (y, format)

'''
plt.plot(t, t, 'ro', t, t**2, 'b|', t, t**3, 'g1')
plt.title("np.arange")
plt.show()

t = np.linspace(0., 5.0, 25)       # (start, stop, num), interval is calculated
plt.plot(t, t, t, t**2,'cp', t, t**3)
plt.title("np.linspace")
plt.show()

"""
format string:  consists of color + line-style
=============
======line style=======
character	description
'-'	    solid line style
'--'	dashed line style
'-.'	dash-dot line style
':'	dotted line style
'.'	point marker
','	pixel marker
'o'	circle marker
'v'	triangle_down marker
'^'	triangle_up marker
'<'	triangle_left marker
'>'	triangle_right marker
'1'	tri_down marker
'2'	tri_up marker
'3'	tri_left marker
'4'	tri_right marker
's'	square marker
'p'	pentagon marker
'*'	star marker
'h'	hexagon1 marker
'H'	hexagon2 marker
'+'	plus marker
'x'	x marker
'D'	diamond marker
'd'	thin_diamond marker
'|'	vline marker
'_'	hline marker

The following color abbreviations are supported:
character	color
‘b’	blue
‘g’	green
‘r’	red
‘c’	cyan
‘m’	magenta
‘y’	yellow
‘k’	black
‘w’	white
"""


"""
Basado en el ejemplo de clase
Solución LAB1
"""

import os
import numpy as np
from matplotlib import pyplot as plt
from scipy.io.wavfile import write as write_wav
from scipy.fft import fft, fftfreq
from scipy.fft import rfft, rfftfreq, irfft

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
audio_filename = os.path.join(BASE_DIR, 'SUCIA.wav')

FS = 44100  # Frecuencia de muestreo
DURATION = 3  # segundos

TONE = 440                 # Tono
NOISE = 3200               # Ruido
muestras_grafica = 1000    # Cantidad de muestras a graficar

def generate_sine_wave(freq, sample_rate=FS, duration=DURATION):
    """Genera una onda senoidal con frecuencia freq
    con una cantidad de muestras = sample_rate * duration"""
    x = np.linspace(0, duration, sample_rate * duration, endpoint=False)
    frequencies = x * freq
    # Sin(wt), w = 2pi*f
    y = np.sin((2 * np.pi) * frequencies)
    return x, y

# Generando tono deaseado
x, y = generate_sine_wave(TONE)
x_limited = x[:muestras_grafica]
y_limited = y[:muestras_grafica]
plt.plot(x_limited, y_limited)
plt.xlabel('Tiempo')
plt.ylabel('Amplitud')
plt.title('Señal pura con frecuencia f')
plt.show()
# Tono Deseado
tone = y

# Generando ruido
_, noise1 = generate_sine_wave(NOISE, FS, DURATION)
mixed_tone = tone + noise1
normalized =  np.int16((mixed_tone / mixed_tone.max()) * 32767)
print('Generando audio con ruido')
write_wav(audio_filename, FS, normalized)
plt.plot(normalized[:muestras_grafica])
plt.xlabel('Tiempo')
plt.ylabel('Amplitud')
plt.title('Señal Tono + Ruido')
plt.show()

# FFT
# Note the extra 'r' at the front
N = FS * DURATION
yf = rfft(normalized)
xf = rfftfreq(N, 1 / FS)
plt.plot(xf, np.abs(yf))
plt.xlabel('Frecuencia')
plt.title('Cuadrante positivo')
plt.show()

# Filtrando la señal
# The maximum frequency is half the sample rate
points_per_freq = len(xf) / (FS / 2)
print(f'points_per_freq {points_per_freq}')
# Our target frequency is noise freq in  Hz
target_idx = int(points_per_freq * NOISE)
yf[target_idx - 1:target_idx + 2] = 0
plt.plot(xf, np.abs(yf))
plt.xlabel('Frecuencia')
plt.title('Filtrado Ruido')
plt.show()

# FFT Inversa
new_sig = irfft(yf)
plt.plot(new_sig[:muestras_grafica])
plt.xlabel('Tiempo')
plt.ylabel('Amplitud')
plt.title('Señal filtrada')
plt.show()

# Generando audio filtrado (limpio)
norm_new_sig = np.int16(new_sig * (32767 / new_sig.max()))
write_wav(
    os.path.join(BASE_DIR, 'LIMPIA.wav'),
    FS, norm_new_sig
)


import numpy as np

class Optimize:

    """
    Instantiate a function.

    :param func: The lambda function to evaluate.
    :type func: Lambda function object

    :param dx: The derivative of the function passed to 'func'. Optional - Used only in Newton-Raphson method.
    :type dx: Lambda function object
    """
    def __init__(self, func, dx = None, ddx = None):
        self.func = func
        self.dx = dx
        self.ddx = ddx

    def golden_search(self, lbound: float, ubound: float, max_itn = 100, delta = 0.000001):
        """
        Finding the root of a function using bisection method.

        :param lbound: The lower bound of the range to evaluate.
        :type lbound: int

        :param ubound: The upper bound of the range to evaluate.
        :type ubound: int

        :param max_itn: The maximum number of iterations to evaluate.
        :type itn: int

        :param delta: The error threshhold.
        :type itn: float

        :return: Numpy array of iteration steps.
        :rtype: ndarray
        """

        phi = (1 + 5**0.5)/2
        a = lbound
        b = ubound
        c = b + (a - b)/phi
        d = a + (b - a)/phi
        fc = self.func(c)
        fd = self.func(d)
        itn = 0
        arr = [itn, a, c, d, b]

        while abs(b - a) >= delta and itn <= max_itn:
            if fc < fd:
                b = d
                d = c
                fd = fc
                c = b + (a - b)/phi
                fc = self.func(c)
            else:
                a = c
                c = d
                fc = fd
                d = (b -  a)/phi
                fd = self.func(d)

            itn += 1
            arr = np.vstack([arr, [itn, a, c, d, b]])

        return arr

    def fibonacci(self, lbound: float, ubound: float, max_itn = 100, delta = 0.0001):
        """
        Finding the root of a function using bisection method.

        :param lbound: The lower bound of the range to evaluate.
        :type lbound: int

        :param ubound: The upper bound of the range to evaluate.
        :type ubound: int

        :param max_itn: The maximum number of iterations to evaluate.
        :type itn: int

        :param delta: The error threshhold.
        :type itn: float

        :return: Numpy array of iteration steps.
        :rtype: ndarray
        """

        a = lbound
        b = ubound
        farr = [1, 1]
        n = 2

        while True:
            farr.append(farr[n-1] + farr[n-2])

            if farr[n] > (b - a)/delta:
                break

            n += 1

        c = a + farr[n-2]/farr[n]*(b-a)
        d = a + farr[n-1]/farr[n]*(b-a)

        fc = self.func(c)
        fd = self.func(d)

        itn = 0

        arr = [itn, a, b, c, d]

        n = n - 1

        while itn <= max_itn and n >= 2: #abs(b - a) >= delta and
            if fc < fd:
                b = d
                d = c
                fd = fc
                c = a + farr[n-2]/farr[n]*(b-a)
                fc = self.func(c)
            else:
                a = c
                c = d
                fc = fd
                d = a + farr[n-1]/farr[n]*(b-a)
                fd = self.func(d)

            itn += 1
            arr = np.vstack([arr, [itn, a, c, d, b]])

            n = n - 1

        return arr

        return

    def newton(self, x0: float, max_itn = 100, delta = 1e-6):
        """
        Finding the root of a function using Newton-Raphson method.

        :param x0: The initial value to evaluate.
        :type x0: int

        :param max_itn: The maximum number of iterations to evaluate.
        :type itn: int

        :param delta: The error threshhold.
        :type itn: float

        :return: Numpy array of iteration steps.
        :rtype: ndarray
        """

        x = x0
        itn = 0

        while itn <= max_itn and abs(self.dx(x)/self.ddx(x)) >= delta:
            x = x - (self.dx(x)/self.ddx(x))

            if itn == 0:
                arr = [itn, x, self.dx(x), self.ddx(x), abs(self.dx(x)/self.ddx(x))]

            else:
                arr = np.vstack([arr, [itn, x, self.dx(x), self.ddx(x), abs(self.dx(x)/self.ddx(x))]])
            itn += 1

        return arr

    def secant(self, x0: float, x1: float, max_itn = 100, delta = 1e-6):
        """
        Finding the root of a function using secant method.

        :param x0: The lower bound of the range to evaluate.
        :type lbound: int

        :param x1: The upper bound of the range to evaluate.
        :type ubound: int

        :param max_itn: The maximum number of iterations to evaluate.
        :type itn: int

        :param delta: The error threshhold.
        :type itn: float

        :return: Numpy array of iteration steps.
        :rtype: ndarray
        """

        a = x0
        b = x1
        lst = [a, b]
        i = 2
        itn = 0

        if self.dx(a)*self.dx(b) > 0:
            return 'Invalid upper and lower limits'

        else:
            while itn <= max_itn and abs(a - b) >= delta:
                lst.append(lst[i-1] - (self.dx(lst[i - 1])*(lst[i-1] - lst[i-2]))/(self.dx(lst[i-1]) - self.dx(lst[i-2])))
                if itn == 0:
                    arr = [itn, i, lst[i-1], lst[i], self.dx(lst[i])]
                else:
                    arr = np.vstack([arr, [itn, i, lst[i-1], lst[i], self.dx(lst[i])]])
                a = lst[i - 2]
                b = lst[i - 1]
                itn += 1
                i += 1
            return arr
        return

    def line_search(self):
        return

    def hooke_jeeves(self):
        return

    def bisection(self, lbound: float, ubound: float, max_itn = 100, delta = 1e-6):
        """
        Finding the root of a function using bisection method.

        :param lbound: The lower bound of the range to evaluate.
        :type lbound: int

        :param ubound: The upper bound of the range to evaluate.
        :type ubound: int

        :param max_itn: The maximum number of iterations to evaluate.
        :type itn: int

        :param delta: The error threshhold.
        :type itn: float

        :return: Numpy array of iteration steps.
        :rtype: ndarray
        """

        a = lbound
        b = ubound
        itn = 0

        if self.dx(a)*self.dx(b) > 0:
            return 'Invalid upper and lower limits'

        else:
            while itn <= max_itn and abs(a-b) >= delta:
                x = (a + b)/2
                if itn == 0:
                    arr = [a, b, x, self.dx(x)]
                else:
                    arr = np.vstack([arr, [a, b, x, self.dx(x)]])
                if self.dx(x) > 0:
                    b = x
                elif self.dx(x) < 0:
                    a = x
                else:
                    return arr
                itn += 1

            return arr

    def steepest_decent():
        return


import matplotlib.pyplot as plt
import numpy as np

def draw(f, start, stop, num=50):
    x = np.linspace(start, stop, num=num)
    y = f(x)
    plt.title("f")
    plt.plot(x, y)
    plt.show()

def softplus(x):
    return np.log(1 + np.exp(x))

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

if __name__ == '__main__':
    draw(sigmoid, -5, 5)


import numpy as np
from rebox import BBoxFormat
from rebox import BBox

def iou(a:BBox, b:BBox):
    """
    Compute the Jaccarrd Index / Intersection over Union of a pair of 2D bounding boxes.

    Args:
        a: BBox object
        b: BBox object

    Returns:
        The IoU of the two bounding boxes.
    """
    # dealing with various formats:
    # unify style to XYXY,
    # keep scale, and account for it in algorithm

    scale = a.format.scale

    unified_format = BBoxFormat("XYXY", scale)

    a = a.as_format(unified_format)
    b = b.as_format(unified_format)

    xA = np.maximum(a.x1, b.x1)
    yA = np.maximum(a.y1, b.y1)
    xB = np.maximum(a.x2, b.x2)
    yB = np.maximum(a.y2, b.y2)

    if scale is None:
        inter_w = xB - xA + 1
    else:
        inter_w = xB - xA

    inter_w = inter_w * (inter_w >= 0)

    if scale is None:
        inter_h = yB - yA + 1
    else:
        inter_h = yB - yA

    inter_h = inter_h * (inter_h >= 0)

    intersection = inter_w * inter_h

    a_width = a.x2 - a.x1
    a_height = a.y2 - a.y1
    a_area = a_width * a_height

    b_width = b.x2 - b.x1
    b_height = b.y2 - b.y1
    b_area = b_width * b_height

    iou = intersection / (a_area + b_area - intersection)

    if np.isinf(iou) or np.isnan(iou):
        iou = 0

    return iou


import numpy as np
import math

def simulate_rss_matrix(num_nodes, area_side, params=None, threshold=None):
    """This function generates a random set of device locations and
      their corresponding RSS matrix

    Parameters:
        num_nodes (int): number of nodes to simulate
        area_side (float): length of one side of the square area, in meters
        params (4-tuple float): (d_ref, power_ref, path_loss_exp, stdev_power)
            d_ref is the reference distance in m
            power_ref is the received power at the reference distance
            path_loss_exp is the path loss exponent
            stdev_power is standard deviation of received Power in dB
        threshold (float): RSS value in dBm below which the radio cannot receive
    Returns:
        (node_locs, rss_matrix): both numpy arrays of float values, where
            node_locs: (num_nodes, 2)-sized array giving x and y
                       coordinates of devices in meters
            rss_matrix: (num_nodes, num_nodes)-sized array giving RSS values
                       between pairs of devices in dB. rss_matrix[i,j] gives
                       RSS at device j when device i transmits. For any entry
                       that is too small, as well as for the diagonal entries,
                       it returns threshold.
    """

    if params is None:
        params = (1.0, -46.123, 2.902, 3.940)
    if threshold is None:
        threshold = -95
    # the above values are arbitrarily chosen "default values"
    # should be changed based on measurements

    node_locs = area_side*np.random.random_sample((num_nodes, 2))

    d_ref = params[0] # reference distance
    power_ref = params[1] # mean received power at reference distance
    path_loss_exp = params[2] # path loss exponent
    stdev_power = params[3] # standard deviation of received power

    #initialize rss_matrix with the radio rss threshold values as default
    rss_matrix = np.full((num_nodes, num_nodes), float(round(threshold,3)))

    for i in range(num_nodes):
        for j in range(num_nodes):
            if i !=j :
                dist = math.sqrt( (node_locs[i,0] - node_locs[j,0])**2 + (node_locs[i,1] - node_locs[j,1])**2)
                rss_matrix[i,j] = power_ref - path_loss_exp*10*math.log10(dist/d_ref) + float(np.random.normal(0, stdev_power, 1)) #lognormal model
                rss_matrix[i,j] = max(rss_matrix[i,j], threshold) #caps minimum
                rss_matrix[i,j] = round(rss_matrix[i][j],2)

    return (node_locs, rss_matrix)

## uncomment the following to test the function out
# if __name__ == '__main__':
#     nl, rm = simulate_rss_matrix(4, 100)
#     print("Node locations array")
#     print(nl)
#     print("RSS Matrix")
#     print(rm)


#########################
##                     ##
## Irving Gomez Mendez ##
##     May 05, 2021    ##
##                     ##
#########################
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import statsmodels.api as sm
from scipy.stats import norm, poisson

# This example considers the number of cases of AIDS in Belgium (data from the 80's).
# We assume that a Poisson model is razonable (See book of Wood S.S. (2006) Generalized Additive Models).
cases = [12,14,33,50,67,74,123,141,165,204,253,246,240]
x = np.array(range(13))
years = x+1981

# significance level
alpha = 0.05

plt.figure(figsize=(10,7.5))
plt.plot(years, cases, 'o')
plt.xlabel('year')
plt.ylabel('cases')
plt.title("Cases of AIDS in Belgium")

n = len(cases)
X = np.vstack([np.ones(n), x]).T

# Poisson regression by hand
b = [3,0.2] # initial values

tolm   = 1e-6       # tolerance (minimum norm of the difference of the betas)
iterm  = 100        # maximum number of iterations
tolera = 1          # initialize tolera
itera  = 0          # initialize ittera
histo  = b          # initialize beta upgrade

while((tolera > tolm) and (itera < iterm)):
  lam    = np.exp(X @ b)
  D      = np.diag(lam)
  var_b  = np.linalg.inv(X.T @ D @ X)
  delta  = var_b @ X.T @ (cases-lam)
  b      = b+delta
  tolera = np.sqrt(sum(delta**2))
  histo  = np.vstack([histo, b])
  itera  = itera+1

histo

# Let's get the significance of the estimators
se_b = np.sqrt(np.diag(var_b))
z_score = b/se_b
p_value = 1-norm.cdf(np.abs(z_score))

z_score
p_value

plt.figure(figsize=(10,7.5))
plt.plot(years, cases, 'o', label='data')
plt.plot(years, lam, 'o', label='predicted')
plt.xlabel('year')
plt.ylabel('cases')
plt.title("Cases of AIDS in Belgium")
plt.legend(loc='best')

# Let's plot using the log(cases)
plt.figure(figsize=(10,7.5))
plt.plot(years, np.log(cases), 'o', label='data')
plt.plot(years, X @ b, 'o', label='estimated')
plt.xlabel('year')
plt.ylabel('cases')
plt.title("log of cases of AIDS in Belgium")
plt.legend(loc='best')

# It looks like there is a lack of structre, lets consider a quadratic fit
X = np.vstack([np.ones(n), x, x**2]).T

# Poisson regression by hand
b = [3,0.2,0.2] # initial values

tolm   = 1e-6       # tolerance (minimum norm of the difference of the betas)
iterm  = 100        # maximum number of iterations
tolera = 1          # initialize tolera
itera  = 0          # initialize ittera
histo  = b          # initialize beta upgrade

while((tolera > tolm) and (itera < iterm)):
  lam    = np.exp(X @ b)
  D      = np.diag(lam)
  var_b  = np.linalg.inv(X.T @ D @ X)
  delta  = var_b @ X.T @ (cases-lam)
  b      = b+delta
  tolera = np.sqrt(sum(delta**2))
  histo  = np.vstack([histo, b])
  itera  = itera+1

# Let's get the significance of the estimators
se_b = np.sqrt(np.diag(var_b))
z_score = b/se_b
p_value = 1-norm.cdf(np.abs(z_score))

p_value

plt.figure(figsize=(10,7.5))
plt.plot(years, cases, 'o', label='data')
plt.plot(years, lam, 'o', label='predicted')
plt.xlabel('year')
plt.ylabel('cases')
plt.title("Cases of AIDS in Belgium")
plt.legend(loc='best')

# Let's plot using the log(cases)
plt.figure(figsize=(10,7.5))
plt.plot(years, np.log(cases), 'o', label='data')
plt.plot(years, X @ b, 'o', label='predicted')
plt.xlabel('year')
plt.ylabel('cases')
plt.title("log of cases of AIDS in Belgium")
plt.legend(loc='upper left')

# Let's add confidence and prediction intervals
upp_conf = X @ b + norm.ppf(1-alpha/2)*np.sqrt(np.diag(X @ var_b @ X.T))
low_conf = X @ b - norm.ppf(1-alpha/2)*np.sqrt(np.diag(X @ var_b @ X.T))

# Let's plot using the log(cases)
plt.figure(figsize=(10,7.5))
plt.fill_between(years, low_conf, upp_conf, facecolor='yellow', alpha=0.5, label=' Approx. confidence interval')
plt.plot(years, np.log(cases), 'o', label='data')
plt.plot(years, X @ b, 'o', label='estimated')
plt.xlabel('year')
plt.ylabel('cases')
plt.title("log of cases of AIDS in Belgium")
plt.legend(loc='upper left')

upp_pred = poisson.ppf(1-alpha/2, lam)
low_pred = poisson.ppf(alpha/2, lam)

plt.figure(figsize=(10,7.5))
plt.fill_between(years, low_pred, upp_pred, facecolor='green', alpha=0.5, label='Prediction interval')
plt.plot(years, cases, 'o', label='data')
plt.plot(years, lam, 'o', label='predicted')
plt.xlabel('year')
plt.ylabel('cases')
plt.title("Cases of AIDS in Belgium")
plt.legend(loc='upper left')

upp_conf_y_scale = lam + norm.ppf(1-alpha/2)*np.sqrt(np.exp(2*X @ b) * np.diag(X @ var_b @ X.T))
low_conf_y_scale = lam - norm.ppf(1-alpha/2)*np.sqrt(np.exp(2*X @ b) * np.diag(X @ var_b @ X.T))

# We can add the confidence interval, with the warning due to Jensen's inequality
plt.figure(figsize=(10,7.5))
plt.fill_between(years, low_pred, upp_pred, facecolor='green', alpha=0.5, label='Prediction interval')
plt.fill_between(years, low_conf_y_scale, upp_conf_y_scale, facecolor='yellow', alpha=0.5, label='Approx. confidence interval')
plt.plot(years, cases, 'o', label='data')
plt.plot(years, lam, 'o', label='predicted')
plt.xlabel('year')
plt.ylabel('cases')
plt.title("Cases of AIDS in Belgium")
plt.legend(loc='upper left')

# Using sm.GLM
poisson_model = sm.GLM(cases, X, family=sm.families.Poisson())
poisson_results = poisson_model.fit()
poisson_results.summary()

b
se_b
z_score
p_value

###

import numpy,from scipy,import statsmodels


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Thu Dec  6 04:54:06 2018

Problem was stated as follows:
    A company must reduce prices by 10%. Profit will be modelled as the charge
    y minus a penalty P(t,dotY) (where dotY is the first derivative of y wr to t
P is given by:
        P(t,dotY) = alpha*dotY**2 + beta*(t**2 - 1)*dotY**3
    We find y that maximizes profif by minimizing:
        S(y) = integrate(P-y,0,1) w.r. to t

    The lagrangian for this problem is:
        L(t,y,dotY) = P(t,dotY) - y                         ...(1)

    with boundary conditions:
        y(0) = 1 and y(1) = 0.9                              ...(2)

for S to be minimal, we calculate Euler-Lagrange equation (e/l-eq) and equalize
it to 0:

    2*alpha*doubleDotY + 6*beta*(t*dotY**2 + (t**2-1)*dotY*doubleDotY) = 0

        where doubleDotY refers to y's second derivative w.r. to t

We explain Differential Transform Method as follows:
    objetive is to approximate y(t) using a it's taylor polynomials.

    for a function w(t), we define for every k>=0, W(k) as the kth derivative of
    w(t) w.r. to t evaluated in t0=0 and devided by k!

    We'll call W(k) the k-th differential transform of w(t)

    it follows that:
        w(t) is approximated by W(0) + W(1)*t + W(2)*t**2 + ... + W(k)*t**k

    using (e/l)-eq, we can derive a recursive method to obtain Y(k) in terms
    of Y(1) (which we'll call gamma)
    This is implemented within DTM file. Lagrangian and euler-lagrange methods
    for this particular set of problems are implemented in PoliticalPricing file.

For this model to approximate an optimal solution. Function y is assumed to be analytic within 0 and 1

@author: Amir
"""

import matplotlib.pyplot as plt
import numpy as np
import DTM
import PoliticalPricing as pp

domain = np.linspace(0,1,30)
k = 10       #Degree of polynomial

#case1 alpha = beta = 0
alpha = 5.0
beta = 5.0

def line(t):
    """
    A straight line connecting (0,1) and (1,0.9)
    """
    return 1.0 - 0.1*t

#fits the paramters of the taylor polynomial
gamma = DTM.findGammaNumerical(k,alpha,beta)
Ys = DTM.diffTrans(k,alpha,beta,False,gamma)

def myModel(t,k=k, Ys=Ys):
    return DTM.model(t,k=k,Ys=Ys)

plt.figure(1)
plt.plot(domain,myModel(domain))
plt.plot(domain,line(domain))
plt.title("Fitted Model vs Straight Line")

plt.figure(2)
plt.plot(domain, pp.eulerEquation(domain,myModel,alpha,beta), label ="My Model")
plt.plot(domain, pp.eulerEquation(domain,line,alpha,beta), label = "Straight line")
plt.plot(domain,[0 for i in domain])
plt.title("Euler-Lagrange equation when alpha=beta=5")
plt.legend(loc = "best")
plt.legend()

sModel = pp.integLag(myModel,alpha,beta)[0]
sLine = pp.integLag(line,alpha,beta)[0]

print("When alpha and beta are both equal to 5.")
print("The integral of the lagrangian in our model is: ")
print(sModel)
print("the integral of the lagrangian using the straight line is: " )
print(sLine)
print("\n")

#case2: alpha != beta

alpha = 7.0/4.0
beta = 5.0

#fits the paramters of the taylor polynomial
#for k in [5,10,15,20,50]:
for k in [5,7]:
    gamma = DTM.findGammaNumerical(k,alpha,beta)
    #gamma = -0.061787462699583774
#    print(gamma)
    Ys = DTM.diffTrans(k,alpha,beta,False,gamma)
#    print(Ys)

    def myModel(t,Ys=Ys):
        """
        The model fitted
        """
        return DTM.model(t, Ys=Ys)

#    print(myModel(1))

    plt.figure(k+2)
    plt.plot(domain,myModel(domain))
    plt.plot(domain,line(domain))
    plt.title("Fitted Model vs Straight Line when alpha != beta in k=" + str(k))

    plt.figure(k+3)
    plt.plot(domain, pp.eulerEquation(domain,myModel,alpha,beta), label = "My Model")
    plt.plot(domain, pp.eulerEquation(domain,line,alpha,beta), label = "Straight line")
    plt.plot(domain,[0 for i in domain])
    plt.title("Euler-Lagrange equation when alpha=7/4 and beta=5")
    plt.legend()

    sModel = pp.integLag(myModel,alpha,beta)[0]
    sLine = pp.integLag(line,alpha,beta)[0]

    print("When alpha equals 7/4 and beta equals 5.")
    print("The integral of the lagrangian in our model is: ")
    print(sModel)
    print("the integral of the lagrangian using the straight line is: " )
    print(sLine)
    print("\n")


"""Simple linear regression implementation.

Author: Karol Oleszek 2019

Model symbols:
x     - explanatory variable
y     - response variable
y^    - predicted response
b0,b1 - estimated regression coefficients
e     - estimation error
y_    - mean y
x_    - mean x
n     - observations count
s     - residual standard error
mse   - mean square error

Model:
y^ = b0 + b1 * x
e = y - y^

Regression:
b0 = y_ - b1 * x_

     nE xy - ExEy
b1 = ---------------
     nEx^2 - (Ex)^2

"""

import math
import numpy as np

class SimpleLinearRegressionModel:
    """Simple linear regression model.
       Features model fitting and predictor.
    """
    def __init__(self, x, y):
        """Initialize model.
        Args:
            x - explanatory variable vector.
            y - response variable vector.
        """
        self.x = np.array(x)
        self.y = np.array(y)
        self.n = len(x)
        self.b0 = 0
        self.b1 = 0
        self.s = 0
        self.mse = 0
        if len(x) != len(y):
            print("Length of response vector doesn't match length of explanatory variable vector")

    def fit(self):
        """Fit model."""
        print("Fitting model...")
        self.b1 = (self.n * sum(self.x * self.y) - sum(self.x) * sum(self.y)) / \
                  (self.n * sum(self.x * self.x) - sum(self.x) ** 2)
        self.b0 = self.y.mean() - self.b1 * self.x.mean()
        print("Model fitted with coefficients: b0 = {0}, b1 = {1}".format(self.b0, self.b1))
        print("Calculating model errors...")
        pred = np.vectorize(self.predict)
        self.mse = (sum(self.y - pred(self.x)) ** 2) / (self.n - 2)
        self.s = math.sqrt(self.mse)
        print("Mean square error: {0}".format(self.mse))
        print("Residual standard error: {0}".format(self.s))

    def predict(self, x_var):
        return self.b0 + x_var * self.b1

if __name__ == "__main__":
    print("Simple linear regression demo.")
    reg_mod = SimpleLinearRegressionModel([1, 2, 332], [3.12, 2.324, 1.2134])
    reg_mod.fit()
    print("Trying predictions:")
    print(1, reg_mod.predict(1))
    print(2, reg_mod.predict(2))
    print(332, reg_mod.predict(332))


# pprint()用于在控制台上漂亮地打印输出。 LaTeX 可以达到最佳效果，例如 在 Jupyter 笔记本中。

from sympy import pprint, Symbol, exp, sqrt
from sympy import init_printing

init_printing(use_unicode=True)

x = Symbol('x')

a = sqrt(2)
pprint(a)
print(a)

print("------------------------")

c = (exp(x) ** 2) / 2
pprint(c)
print(c)


import numpy as np

def mean_normalize(X):
    return X - np.mean(X, axis=0)

def standardize(X, with_mean=True, with_std=True):
    if with_mean:
        X = mean_normalize(X)
    if with_std:
        X = X / np.std(X, axis=0)
    return X

def minmax_normailze(X):
    min_ = np.min(X, axis=0)
    max_ = np.max(X, axis=0)
    return (X - min_) / (max_ - min_)

def maxabs_normalize(X):
    min_ = np.min(X, axis=0)
    max_ = np.max(X, axis=0)
    maxabs = np.maximum(min_, max_)
    return X / maxabs


# Machine Learning Online Class
#  Exercise 7 | Principle Component Analysis and K-Means Clustering
#
#  Instructions
#  ------------
#
#  This file contains code that helps you get started on the
#  exercise. You will need to complete the following functions:
#
#     pca.m
#     projectData.m
#     recoverData.m
#     computeCentroids.m
#     findClosestCentroids.m
#     kMeansInitCentroids.m
#
#  For this exercise, you will not need to change any code in this file,
#  or any other files other than those mentioned above.
#

import numpy
import matplotlib.pyplot as plt

from skimage.io import imread
from scipy.io import loadmat

import kmeans_pca_funcs

# ================== Part 1: Load Example Dataset  ===================
print("Visualizing example dataset for PCA.\n\n")

#  The following command loads the dataset. You should now have the variable X in your environment
data = loadmat("./ex7data1.mat")
x_data = data['X']
num_examples, num_features = x_data.shape
print("Ex.7.2 PCA #training examples:", num_examples, "#features:", num_features, "\n")

#  Visualize the example dataset
plt.figure(dpi=120)
plt.title("PCA dataset 1")
plt.xlabel("Feature 1")
plt.ylabel("Feature 2")
plt.scatter(x_data[:, 0], x_data[:, 1], marker='o', s=16, linewidths=1.0, color='blue')
plt.xlim(0.5, 6.5)
plt.ylim(2, 8)
plt.gca().set_aspect('equal')
plt.grid(b=True, which='major', axis='both', linestyle='--', linewidth=0.5)
plt.tight_layout()
plt.show()

# =============== Part 2: Principal Component Analysis ===============
#  Before running PCA, it is important to first normalize X
x_norm, mu, sigma = kmeans_pca_funcs.feature_normalize(x_data)
mat_U, mat_S = kmeans_pca_funcs.pca(x_norm)  # Run PCA

#  Draw the eigenvectors centered at mean of data. These lines show the directions of maximum variations in the dataset.
fig, ax = plt.subplots()
plt.title("Eigenvectors")
ax.scatter(x_data[:, 0], x_data[:, 1], marker='o', s=16, linewidths=1.0, color='blue')

for i in range(num_features):
    ax.arrow(mu[0], mu[1], 1.5*mat_S[i]*mat_U[0, i], 1.5*mat_S[i]*mat_U[1, i],
             head_width=0.25, head_length=0.2, fc='k', ec='k', lw=2, zorder=1000)

ax.axis([0.5, 6.5, 2, 8])
ax.set_aspect('equal')
ax.grid(b=True, which='major', axis='both', linestyle='--', linewidth=0.5)
fig.tight_layout()
fig.show()

print("Computed eignevector = [{:.6f} {:.6f}]".format(mat_U[0, 0], mat_U[1, 0]))
print("Expected eigenvector = [-0.707107 -0.707107]")

# =================== Part 3: Dimension Reduction ===================
#  Project the data onto K = 1 dimension
ndims_K = 1
projection_Z = kmeans_pca_funcs.project_data(x_norm, mat_U, ndims_K)
print("\nComputed projection value: {:.6f}".format(projection_Z[0, 0]))
print("Expected projection value: 1.481274")

recovered_X = kmeans_pca_funcs.recover_data(projection_Z, mat_U, ndims_K)
print("\nComputed recovered value of the first example: [{:.6f} {:.6f}]".format(recovered_X[0, 0], recovered_X[0, 1]))
print("Expected recovered value of the first example: [-1.047419 -1.047419]")

#  Plot the normalized dataset (returned from featureNormalize)
fig, ax = plt.subplots(figsize=(5, 5), dpi=120)
plt.title("Recovered data")
ax.scatter(x_norm[:, 0], x_norm[:, 1], marker='o', s=16, linewidths=1.0, color='blue')
ax.set_aspect('equal')
plt.axis([-3, 2.75, -3, 2.75])
ax.grid(b=True, which='major', axis='both', linestyle='--', linewidth=0.5)

# Draw lines connecting the projected points to the original points
ax.scatter(recovered_X[:, 0], recovered_X[:, 1], marker='o', s=16, linewidths=1.0, color='red')
for xnorm, xrec in zip(x_norm, recovered_X):
    ax.plot([xnorm[0], xrec[0]], [xnorm[1], xrec[1]], '--k', lw=1)

fig.tight_layout()
fig.show()

# =============== Part 4: Loading and Visualizing Face Data =============
#  Load Face dataset
data = loadmat('./ex7faces.mat')
x_faces = data['X']
num_examples, num_features = x_faces.shape
print("\nEx.7.3 Faces PCA #training examples:", num_examples, "#features:", num_features, "\n")

#  Display the first 100 faces in the dataset
kmeans_pca_funcs.display_data(x_faces[:100, :], figsize=(8, 8))

# =========== Part 5: PCA on Face Data: Eigenfaces  ===================
# normalize X by subtracting the mean value from each feature
x_faces_norm, mu, sigma = kmeans_pca_funcs.feature_normalize(x_faces)

faces_U, faces_S = kmeans_pca_funcs.pca(x_faces_norm)  # Run PCA

# Visualize the top 36 eigenvectors found
kmeans_pca_funcs.display_data(numpy.transpose(faces_U[:, :36]), figsize=(8, 8))

# ============= Part 6: Dimension Reduction for Faces =================
#  Project images to the eigen space using the top k eigenvectors if you are applying a machine learning algorithm
ndims_K = 100

faces_Z = kmeans_pca_funcs.project_data(x_faces_norm, faces_U, ndims_K)

print("The projected data Z has a shape of:", faces_Z.shape)

# # ==== Part 7: Visualization of Faces after PCA Dimension Reduction ====
#  Project images to the eigen space using the top K eigen vectors and visualize only using those K dimensions
#  Compare to the original input, which is also displayed
ndims_K = 100
recovered_faces_X = kmeans_pca_funcs.recover_data(faces_Z, faces_U, ndims_K)

# Display normalized data
kmeans_pca_funcs.display_data(x_faces_norm[:100, :], figsize=(6, 6))
plt.gcf().suptitle('Original faces')

# Display reconstructed data from only k eigenfaces
kmeans_pca_funcs.display_data(recovered_faces_X[:100, :], figsize=(6, 6))
plt.gcf().suptitle('Recovered faces')

# === Part 8(a): Optional (ungraded) Exercise: PCA for Visualization ===

# # If imread does not work for you, you can try instead
img = imread("./bird_small.png")
img = img/255
img_2D = img.reshape(-1, 3)

# perform the K-means clustering again here
num_centroids_K = 16
max_iters = 10
initial_centroids = kmeans_pca_funcs.kmeans_init_centroids(img_2D, num_centroids_K)
centroids, idx = kmeans_pca_funcs.run_kmeans(
    kmeans_pca_funcs.find_closest_centroids, kmeans_pca_funcs.compute_centroids, img_2D, initial_centroids, max_iters, False)

#  Sample 1000 random indexes (since working with all the data is too expensive. If you have a fast computer, you may increase this.
sel = numpy.random.choice(img_2D.shape[0], size=1000)

fig = plt.figure(figsize=(6, 6), dpi=120)
ax = fig.add_subplot(111, projection='3d')

ax.scatter(img_2D[sel, 0], img_2D[sel, 1], img_2D[sel, 2], cmap='rainbow', c=idx[sel], s=8**2)
ax.set_title('Pixel dataset plotted in 3D.\nColor shows centroid memberships')
fig.show()

# === Part 8(b): Optional (ungraded) Exercise: PCA for Visualization ===
# Subtract the mean to use PCA
img_2D_norm, mu, sigma = kmeans_pca_funcs.feature_normalize(img_2D)

# PCA and project the data to 2D
U, S = kmeans_pca_funcs.pca(img_2D_norm)
Z = kmeans_pca_funcs.project_data(img_2D_norm, U, 2)

fig = plt.figure(figsize=(6, 6), dpi=120)
ax = fig.add_subplot(111)

ax.scatter(Z[sel, 0], Z[sel, 1], cmap='rainbow', c=idx[sel], s=64)
ax.set_title('Pixel dataset plotted in 2D, using PCA for dimensionality reduction')
ax.grid(b=True, which='major', axis='both', linestyle='--', linewidth=0.5)
fig.show()

print("\n\n\nDone\n\n\n")


import numpy as np

# Array Creation methods
a = np.empty( (2,3,4), dtype= int )     # Randomly UNinitilizes the array with random integer
a_zeros = np.zeros((2,3,1), dtype= int) # All initilizes to zero
a_zeros = np.zeros((2,3,1), dtype= 'i4') # All initilizes to zero float
x = np.zeros((2,2), dtype = [('x', 'i4'), ('y', 'i4')])     # Initilize with other datatype
x = np.ones([2,2], dtype = int)             # Similar to zeros

# Converting any python datatype to numpy arrays
# numpy.asarray(a, dtype = None, order = None)
# Where a --> Input data in any form such as list, list of tuples, tuples, tuple of tuples or tuple of lists

# Convert from a list of numbers
x = [ [1,2,3], [4,5,6] ]
a = np.asarray(x, dtype = float)
print(a)

# Convert from a array of tuples
x = [(1,2,3),(4,5)]
a = np.asarray(x)
print(a)

# Numpy range functions
# Syntax: numpy.arange(start, stop, step, dtype)

a = np.arange(2, 20, 3)
print("Array from range: ", a)

# Linear space function
# Syantax: numpy.linspace(start, stop, num, endpoint, retstep, dtype)
# num --> number of entries to take
# endpoint --> boolean to include the end number or not, default = True
x = np.linspace(10,20, 5, endpoint = False)
print("Linear array is: ", x)

# Log space function
# Synatax: numpy.logspace(start, stop, num, endpoint, base, dtype)
# Similar to linera space
a = np.logspace(1,10,num = 10, base = 2)
print("Log space array: ", a)


# %%
import math
import operator

import numpy as np
from IPython.display import display
from toolz import reduce

# %% [markdown]
# # Computation in NumPy
#
# Computations can be performed of tensors with compatible dimensions:

# %%
v1 = np.array([2, 4, 5])
v2 = np.arange(3.0)

# %%
v1 + v2

# %%
v1 * v2

# %%
v1 @ v2

# %%
v1.dot(v2)

# %%
m1 = np.arange(12).reshape(3, 4)
m2 = np.ones((3, 4))
m3 = np.array([[1, 3], [5, 7], [2, 4], [6, 8]])
print("m1.shape:", m1.shape, "m2.shape:", m2.shape, "m3.shape:", m3.shape)

# %%
m1 + m2

# %%
# m1 + m3

# %%
m1 @ m3

# %% [markdown]
# ## Boolean Operations

# %%
v1 = np.arange(4)
v2 = np.ones(4)

# %%
v1 == v2  # noqa B015

# %%
v1 <= v2  # noqa B015

# %%
# if v1 == v2: print("Done")

# %%
equals = v1 == v2
equals

# %%
equals.all()

# %%
equals.any()

# %%
if equals.any():
    print("Done")

# %% [markdown]
# ## Broadcasting (Part 1)
#
# Most operations in NumPy can be used with scalars as well:

# %%
v1 = np.arange(8)
v1

# %%
v1 + 5

# %%
3 + v1

# %%
v1 * 2

# %%
v1 ** 2

# %%
2 ** v1

# %%
v1 > 5  # noqa B015

# %% [markdown]
# ## Minimum, Maximum, Sum, ...

# %%
np.set_printoptions(precision=2)

# %%
rng = np.random.default_rng(42)
vec = rng.random(10)
vec

# %%
vec.max()

# %%
vec.argmax()

# %%
vec[vec.argmax()]

# %%
vec.min()

# %%
vec.argmin()

# %%
rng = np.random.default_rng(42)
arr = rng.random((3, 5))

# %%
arr.max()

# %%
arr.argmax()

# %%
arr.min()

# %%
arr.argmin()

# %%
arr.reshape(-1)[arr.argmin()]

# %%
# arr[np.unravel_index(arr.argmin(), arr.shape)]

# %%
arr

# %%
arr.sum()

# %%
arr.sum(axis=0)

# %%
arr.sum(axis=1)

# %%
arr.mean()

# %%
arr.mean(axis=0)

# %%
arr.mean(axis=1)

# %%
v1 = np.arange(2)
v2 = np.linspace(1, 3, 3)

# %%
np.concatenate([v1, v2])

# %%
m1 = np.arange(12).reshape(3, 4)
m2 = np.arange(12, 24).reshape(3, 4)

# %%
m1

# %%
m2

# %%
np.concatenate([m1, m2])

# %%
np.concatenate([m1, m2], axis=1)

# %%
m3 = np.arange(12, 15).reshape(3, -1)
m3

# %%
# np.concatenate([m1, m3])

# %%
np.concatenate([m1, m3], axis=1)

# %% [markdown]
# ## Indices for NumPy Arrays

# %%
vec = np.arange(10)

# %%
vec

# %%
vec[3]

# %%
vec[3:8]

# %%
vec[-1]

# %%
arr = np.arange(24).reshape(4, 6)

# %%
arr

# %%
arr[1]

# %%
arr[1][2]

# %%
arr[1, 2]

# %%
arr

# %%
arr[1:3]

# %%
arr[1:3][2:4]

# %%
arr[1:3, 2:4]

# %%
arr[:, 2:4]

# %%
# Danger!
arr[:2:4]

# %%
arr[:, 1:6:2]

# %% [markdown]
# ## Slices and Modifications
#
# It's possible to apply operations to slices. Modification of slices *changes
# the underlying array.*

# %%
arr = np.ones((3, 3))
arr

# %%
arr[1:, 1:] = 2.0

# %%
arr

# %%
lst = [1, 2, 3]
vec = np.array([1, 2, 3])

# %%
lst[:] = [99]

# %%
lst

# %%
vec[:] = [99]

# %%
vec

# %%
vec[:] = 11
vec

# %% [markdown]
# ## Danger!
# Don't use the `lst[:]` Idiom!

# %%
lst1 = list(range(10))
lst2 = lst1[:]
lst1[:] = [22] * 10
print(lst1)
print(lst2)

# %%
vec1 = np.arange(10)
vec2 = vec1[:]
vec1[:] = 22
print(vec1)
print(vec2)

# %%
vec1 = np.arange(10)
vec2 = vec1.copy()
vec1[:] = 22
print(vec1)
print(vec2)

# %% [markdown]
#
# Similar considerations hold for reshaped arrays:

# %%
vec = np.arange(4)
arr = vec.reshape(2, 2)
arr

# %%
arr[1, 1] = 10
vec[0] = 20
arr

# %%
vec

# %% [markdown]
# ### Boolean Operations on NumPy Arrays

# %%
bool_vec = np.array([True, False, True, False, True])

# %%
neg_vec = np.logical_not(bool_vec)
neg_vec

# %%
np.logical_and(bool_vec, neg_vec)

# %%
~bool_vec

# %%
bool_vec & neg_vec

# %%
bool_vec | neg_vec

# %% [markdown]
# ## Conditional Selection
#
# You can use a NumPy array with Boolean values as index value, if it has the
# same shape as the "value array". This will select all elements of the value
# array for which the index evaluates to true.

# %%
vec = np.arange(9)
bool_vec = vec % 3 == 0
print(vec)
print(bool_vec)

# %%
vec[bool_vec]

# %%
arr = np.arange(8).reshape(2, 4)
bool_arr = arr % 2 == 0
bool_arr

# %%
arr[bool_arr]

# %%
# Error!
# arr[bool_arr.reshape(-1)]

# %%
vec[vec % 2 > 0]

# %%
arr[arr < 5]

# %%
arr = np.arange(30).reshape(6, 5)
arr

# %%
arr[:, 1]

# %%
arr[:, 1] % 2 == 0  # noqa B015

# %%
arr[arr[:, 1] % 2 == 0]

# %%
arr[arr[:, 1] % 2 == 1]

# %% [markdown]
# ## Universal NumPy Functions
#
# NumPy offers a wealth of universal functions that work on NumPy arrays, lists,
# and often numbers

# %%
vec1 = rng.random(5)
vec2 = rng.random(5)
display(vec1)

list1 = list(vec1)
list2 = list(vec2)
display(list1)

matrix = np.arange(6).reshape(2, 3)
list_matrix = [[0, 1, 2], [3, 4, 5]]
display(matrix)
display(list_matrix)

# %%
vec1.sum()

# %%
# list1.sum()

# %%
reduce(operator.add, list1, 0)

# %%
reduce(operator.add, vec1, 0)

# %%
np.sum(vec1)

# %%
np.sum(list1)

# %%
np.sum(matrix)

# %%
np.sum(list_matrix)

# %%
np.sum(123)

# %%
np.sum(list_matrix, axis=0)

# %%
np.sin(vec1)

# %%
np.sin(list1)

# %%
np.sin(matrix)

# %%
np.sin(list_matrix)

# %%
np.sin(math.pi)

# %%
np.mean(vec1)

# %%
np.median(vec1)

# %%
np.std(vec1)

# %%
np.greater(vec1, vec2)

# %%
np.greater(list1, list2)

# %%
np.greater(vec1, list2)

# %%
display(vec1)
display(vec2)

# %%
np.maximum(vec1, vec2)

# %%
np.maximum(list1, list2)

# %%
np.maximum(list1, vec2)

# %% [markdown]
#
# A complete list of universal functions is
# [here](https://numpy.org/doc/stable/reference/ufuncs.html).

# %% [markdown]
#
# ## Broadcasting (Part 2)

# %%
arr = np.arange(16).reshape(2, 2, 4)
print(f"arr.shape: {arr.shape}")
arr

# %%
arr * arr

# %%
3 * arr

# %%
vec1 = np.arange(3)
display(vec1)
print(f"vec1.shape: {vec1.shape}")
print(f"arr.shape:  {arr.shape}")
# arr * vec1

# %%
vec2 = np.arange(4)
display(arr)
display(vec2)
print(f"vec2.shape: {vec2.shape}")
print(f"arr.shape:  {arr.shape}")
arr * vec2

# %% [markdown]
#
# ### Rules for broadcasting:
#
# When performing an operation on `a` and `b`:
#
# - Axes (shapes) of `a` and `b` are compared from right to left
#
# - If `a` and `b` have the same length for an axis, they are compatible
#
# - If either `a` or `b` has length 1 for an axis, it is conceputally repeated
#   along this axis to fit the other array
#
# - If `a` and `b` have different lengths along an axis and neither has length 1
#   they are incompatible
#
# - The array with lower rank is treated as if it has rank 1 for the missing
#   axes, the missing axes are appended on the left

# %%
def ones(shape):
    return np.ones(shape, dtype=np.int32)

# %%
def tensor(shape):
    from functools import reduce
    from operator import mul

    size = reduce(mul, shape, 1)
    return np.arange(1, size + 1).reshape(*shape)

# %%
tensor((2, 3))

# %%
tensor((1, 3))

# %%
tensor((2, 1))

# %%
ones((1, 3)) + tensor((2, 1))

# %%
np.concatenate([tensor((2, 1))] * 3, axis=1)

# %%
ones((1, 3))

# %%
np.concatenate([ones((1, 3))] * 2, axis=0)

# %%
ones((1, 3)) + tensor((2, 1))

# %%
tensor((1, 3)) + ones((2, 1))

# %%
tensor((1, 3)) + tensor((2, 1))

# %%
tensor((2, 3, 4))

# %%
tensor((2, 3, 1))

# %%
tensor((2, 1, 4))

# %%
ones((2, 3, 1)) + tensor((2, 1, 4))

# %%
ones((2, 3, 1))

# %%
np.concatenate([ones((2, 3, 1))] * 4, axis=2)

# %%
tensor((2, 1, 4))

# %%
np.concatenate([tensor((2, 1, 4))] * 3, axis=1)

# %%
ones((2, 3, 1)) + tensor((2, 1, 4))

# %%
tensor((2, 3, 1)) + ones((2, 1, 4))

# %%
tensor((2, 3, 1)) + tensor((2, 1, 4))

# %%
tensor((3, 1)) + tensor((2, 1, 4))

# %%
tensor((3, 1))

# %%
tmp1 = np.concatenate([tensor((3, 1))] * 4, axis=1)
print("Shape:", tmp1.shape)
tmp1

# %%
tmp2 = tmp1.reshape(1, 3, 4)
print("Shape:", tmp2.shape)
tmp2

# %%
tmp3 = np.concatenate([tmp2] * 2, axis=0)
print("Shape:", tmp3.shape)
tmp3

# %%
tensor((2, 1, 4))

# %%
tmp4 = np.concatenate([tensor((2, 1, 4))] * 3, axis=1)
print("Shape:", tmp4.shape)
tmp4

# %%
display(tmp3)
display(tmp4)

# %%
tmp3 + tmp4

# %%


"""
My implementation of the binary search algorithm. This algorithm works only on sorted arrays.
"""
import numpy as np

def binarysearch(num, array, lo=0, hi=None):
    N = len(array)

    #First call
    if hi == None: hi = N

    #Number not found
    if lo >= hi:
        print "Number not found. Returning -1 as an index."
        return -1

    mid = lo + (hi-lo)/2
    #print lo, mid, hi
    if array[mid] == num: return mid
    elif array[mid] > num: return binarysearch(num, array, lo, mid)
    #array[mid] < num:
    return binarysearch(num, array, mid+1, hi)

if __name__ == "__main__":
    #Create a sorted list of integers
    np.random.seed(123456789)
    data = np.random.randint(low=0, high=1000, size=100)
    data = np.sort(data)

    number = data[np.random.randint(low=0, high=100, size=1)][0]
    print "Finding the number: ", number
    index = binarysearch(number, data)
    print "Found at index: ", index


import numpy as np
import matplotlib.pyplot as plt

#=============================================================================
# Basic linear algebra operations
#=============================================================================

# Defining matrices

A = np.random.rand(3, 2) # 3x2 random matrix
print("A =\n" + str(A))

B = np.array([[4, -3, 1], [9, 3, 2], [-1, 0, 6]]) # 3x3 mat def elementwise
print("\nB =\n" + str(B))

C = np.eye(2) # 2x2 identity matrix
print("\nC =\n" + str(C))

D = np.zeros((3, 3)) # 3x3 matrix filled using a loop
for i in range(3):
    for j in range(3):
        D[i][j] = 3*i + j + 1 # Note that Python indices begin at 0
print("\nD =\n" + str(D))

E = np.block([[A, B], [C, A.T]]) # 5x5 matrix defined as a block matrix
print("\nE =\n" + str(E))

# Basic matrix operations

print("\ndet(B) =")
print(np.linalg.det(B)) # Matrix determinant

print("\ninv(B) =")
print(np.linalg.inv(B)) # Matrix inverse

print("\nB' =")
print(B.T) # Matrix transpose

print("\n3B =")
print(3*B) # Scalar multiplication

print("\nBD =")
print(B@D) # Matrix-matrix product

print("\nB.D =")
print(B*D) # Elementwise matrix-matrix product

# Defining vectors

# Note that NumPy does not distinguish between column and row vectors, so
# anything that specifically requires a column or row vector requires
# first converting the vector into a 2D array.

u = np.random.rand(3) # Length 3 random vector
print("\nu =\n" + str(u))

v = np.array([3, -4, 8, 0, -1, 2]) # Length 6 vector defined elementwise
print("\nv =\n" + str(v))

w = np.zeros(5) # Redefining a range of vector values
w[1:4] = 1
print("\nw =\n" + str(w))

x = np.linspace(0, 2*np.pi, 101) # Vector of equally-spaced values on [0,2pi]
print("\nx =\n" + str(x[:11]) + "\n...\n" + str(x[-10:]))

y = np.array([i for i in range(10, -2, -2)]) # Values defined by a range
print("\ny =\n" + str(y))

# Basic vector operations

print("\n2v =")
print(2*v) # Scalar multiplication

print("\nv'y =")
print(np.inner(v, y)) # Inner product

print("\nvy' =")
print(np.outer(v, y)) # Outer product

print("\nv.y =")
print(v*y) # Elementwise vector-vector product

print("\nmin(v) =")
print(min(v)) # Minimum value

print("\nmax(v) =")
print(max(v)) # Maximum value

print("\nmean(v) =")
print(np.mean(v)) # Average value

print("\n||v||_2 =")
print(np.linalg.norm(v)) # Euclidean norm

print("\n||v||_1 =")
print(np.linalg.norm(v, 1)) # Taxicab norm

print("\n||v||_inf =")
print(np.linalg.norm(v, np.inf)) # Chebyshev norm

# Eigenvalues

# Note that NumPy's eig() function always returns two arrays: A 2D array of
# eigenvectors, and a 1D array of eigenvalues, respectively. The separate
# eigvals() function returns only the eigenvalues.

ew = np.linalg.eigvals(B) # Vector of eigenvalues
print("\nEigenvalues of B:\n" + str(ew))

ew, ev = np.linalg.eig(B) # Matrices of eigenvalues and eigenvectors
print("\nEigenvectors of B:\n" + str(ev))

print("\nX Lambda X^(-1) =")
print(ev @ np.diag(ew) @ np.linalg.inv(ev)) # Eigendecomposition of B

# Linear systems

v = v[:3]
z = np.linalg.solve(B, v) # Solve the linear system Bz = v
print("\nz =\n" + str(z))

print("\nBz =")
print(B@z) # Matrix-vector product

#=============================================================================
# Basic programming structures
#=============================================================================

# Conditionals

if np.linalg.det(B) > 0:
    print("\ndet(B) is positive")
elif np.linalg.det(B) < 0:
    print("\ndet(B) is negative")
else:
    print("\ndet(B) is zero")

# For loops

s = np.zeros(10)
for i in range(len(s)):
    s[i] = np.sqrt(i+1) # Square root
print("\ns =\n" + str(s))

# While loops

p = 0
while 2**p < 1000:
    p += 1 # Combined assignment
print("\np =\n" + str(p))

# Functions

def square(x):
    return x**2

print("\nsquare(y) =\n" + str(square(y)))

#=============================================================================
# Basic plotting
#=============================================================================

# Single 2D plot

# In Pyplot, multiple series can be plotted on the same axis either by passing
# all of them to the plot() function, or plotting them separately before using
# the show() function.
# Series labels in the legend are most easily added by defining them in the
# 'label' keyword argument of the respective plot() function before calling the
# legend() function.

y1 = np.cos(x) # Generate function values on [0,2pi]
y2 = np.sin(x)

plt.figure() # Create a new figure window

plt.plot(x, y1, 'r', label="cos(x)") # Plot a red solid line
plt.plot(x, y2, 'b.', label="sin(x)") # Plot a blue dotted line
plt.title("Example 1") # Figure title
plt.xlabel("x") # x-axis label
plt.ylabel("y") # y-axis label
plt.legend() # Plot legend (automatically adopts legend keywords from plots)

# Stacked 2D plots

plt.figure()
plt.subplot(2, 1, 1) # Target first tile in a 2x1 layout
plt.plot(x, y1, 'r') # Plot first function
plt.title("Example 2") # Add title above first subplot
plt.subplot(2, 1, 2) # Target second tile in a 2x1 layout
plt.plot(x, y2, 'b.') # Plot second function

# 3D surface plots

# Surface plots are usually generated with the help of NumPy's meshgrid()
# function, which returns a pair of matrices: One whose rows are identical
# copies of the x-values, and one whose columns are identical copies of the
# y-values. The mesh of z-coordinates can then be computed by evaluating an
# elementwise function with these two functions as the x- and y-values.

[X, Y] = np.meshgrid(np.linspace(-4,4,41), np.linspace(-4,4,41)) # Grids

def f(x, y): # Define a function for the surface
	return 100*np.cos(x) + 1 - 10*y**2 + y**4

Z = f(X, Y) # Generate z-coordinates

plt.figure()
ax = plt.axes(projection='3d') # Get handle for 3D axis in current figure
ax.plot_surface(X, Y, Z) # Surface plot
plt.title("Example 3")

plt.show() # Show all generated figures


# coding: utf-8

# # Lab 4- Decision Trees

# This assignment uses 2012 data obtained from the Federal Election Commission on contributions to candidates from committees. The data dictionary is available at http://www.fec.gov/finance/disclosure/metadata/DataDictionaryContributionstoCandidates.shtml. The file we've given you has been subset to 10,000 randomly sampled rows, wth some columns removed

# In[48]:

from __future__ import division, print_function
from collections import Counter, defaultdict
from itertools import combinations
import pandas as pd
import numpy as np
import itertools

# In[49]:

import sklearn
from sklearn.tree import DecisionTreeClassifier
from sklearn.feature_extraction import DictVectorizer #to turn categorial variables into numeric arrays
from sklearn import preprocessing #to transform the feature labels
from sklearn.feature_extraction import DictVectorizer

# In[50]:

df = pd.read_csv('lab4_candidate_contributions.csv')

#convert zip code and transaction date from floats to strings (since we wnat to treat them as categorical)
df.ZIP_CODE = df.ZIP_CODE.astype('int').astype('str')
df.TRANSACTION_DT = df.TRANSACTION_DT.astype('int').astype('str')

df.head()

# In[ ]:

# ## Calculating Gini Index

#
#
# **Question 1: How many rows are there in the dataset for Obama? For Romney? **
#
#

# In[51]:

obama = 0
romney = 0
for i in range(df.CAND_ID.size):
    if df.CAND_ID.get_value(i) == "Obama":
        obama += 1
    else:
        romney += 1
print("Obama: %d, Romney: %d"%(obama, romney))

# **Question 2: What is the Gini Index of the this dataset, using Romney and Obama as the target classes?**

# In[52]:

def gini(D):
    obama = sum(D.CAND_ID == "Obama")
    romney = sum(D.CAND_ID == "Romney")
    total = obama+romney
    return 1-((obama/total)**2+(romney/total)**2)

print("gini index: %f"%gini(df))

# ## Best Split of a Numeric Feature

# In[53]:

sortd = df.sort(columns="TRANSACTION_AMT")
mingini = 1
mini = 0
ob1 = 0
ob2 = sum(df.CAND_ID == "Obama")
ro1 = 0
ro2 = sum(df.CAND_ID == "Romney")
total = ob2+ro2
for i in range(df.CAND_ID.size-1):
    if sortd.CAND_ID.get_value(i) == "Obama":
        ob1 += 1
        ob2 -= 1
    else:
        ro1 += 1
        ro2 -= 1
    low = df.TRANSACTION_AMT.get_value(i)
    high = df.TRANSACTION_AMT.get_value(i+1)
    if low != high:
        tot1 = ob1+ro1
        tot2 = ob2+ro2
        gini1 = 1-((ob1/tot1)**2+(ro1/tot1)**2)
        gini2 = 1-((ob2/tot2)**2+(ro2/tot2)**2)
        ginit = gini1*((ob1+ro1)/total) + gini2*((ob2+ro2)/total)
        if ginit < mingini:
            mini = i
            mingini = ginit
            minob1 = ob1
            minob2 = ob2
            minro1 = ro1
            minro2 = ro2

print("split after: %d, gini score: %f, gini reduced by: %f, Obama below: %d, Obama above: %d, Romney below: %d, Romney above: %d"%(mini, mingini, (gini(df)-mingini), minob1, minob2, minro1, minro2))

# **Question 3: What is the best split point of the TRANSACTION_AMT feature. **

# In[54]:

mini

# **Question 4: What is the Gini Index of this best split?**

# In[55]:

mingini

# **Question 5: How much does this partitioning reduce the Gini Index over that of the overall dataset?**

# In[56]:

(gini(df)-mingini)

# **Question 6: How many Romney rows are below your best split point? Obama rows?**

# In[57]:

print("Romney Rows: ", minro1)
print("Obama Rows:", minob1)

# **Question 7: How many Romney rows are above your best split point? Obama rows?**

# Recall that, to calculate the best split of this numeric field, you'll need to order your data by TRANSACTION AMT, then consider the midpoint between each pair of consecutive transaction amounts as a potential split point, then calculate the Gini Index for that partitioning. You'll want to keep track of the best split point and its Gini Index (remember that you are trying to minimize the Gini Index).
#
# There are a lot of ways to do this. Some are very fast, others very slow. One tip to make this run quickly is, as you consecutively step through the data and calculate the Gini Index of each possible split point, keep a running total of the number of rows for each candidate that are located above and below the split point.
#
# Some Python tips:
#
# * Counter(), from the collections module, is a special dictionary for counting values of a key
# * zip() lets you concatenate lists into a list of tuples (for example, if we have a list of the candidates and a list of transaction amounts, zip(candidate_list, transaction_amount) would give us a list of (candidate, transaction amount) pairs

# In[58]:

sortd = df.sort(columns="TRANSACTION_AMT")
mingini = 1
mini = 0
ob1 = 0
ob2 = sum(df.CAND_ID == "Obama")
ro1 = 0
ro2 = sum(df.CAND_ID == "Romney")
total = ob2+ro2
for i in range(df.CAND_ID.size-1):
    if sortd.CAND_ID.get_value(i) == "Obama":
        ob1 += 1
        ob2 -= 1
    else:
        ro1 += 1
        ro2 -= 1
    low = df.TRANSACTION_AMT.get_value(i)
    high = df.TRANSACTION_AMT.get_value(i+1)
    if low != high:
        tot1 = ob1+ro1
        tot2 = ob2+ro2
        gini1 = 1-((ob1/tot1)**2+(ro1/tot1)**2)
        gini2 = 1-((ob2/tot2)**2+(ro2/tot2)**2)
        ginit = gini1*((ob1+ro1)/total) + gini2*((ob2+ro2)/total)
        if ginit < mingini:
            mini = i
            mingini = ginit
            minob1 = ob1
            minob2 = ob2
            minro1 = ro1
            minro2 = ro2

print("split after: %d, gini score: %f, gini reduced by: %f, Obama below: %d, Obama above: %d, Romney below: %d, Romney above: %d"%(mini, mingini, (gini(df)-mingini), minob1, minob2, minro1, minro2))

# ## Best Split of a Categorial Variable

# In[59]:

import functools
# question 8
entity_vals = pd.unique(df["ENTITY_TP"])
combinations = functools.reduce(lambda x,y: x+y, [list(itertools.combinations(entity_vals, r)) for r in range(1, len(entity_vals)//2 + 1)])

# question 9
mingini = 1
mincomb = None
for comb in combinations:
    indices = df["ENTITY_TP"].isin(comb)
    split1 = df.loc[indices,:]
    split2 = df.loc[~indices,:]
    cur_gini = (len(split1) * gini(split1) + len(split2) * gini(split2)) / len(df)
    if cur_gini < mingini:
        mingini = cur_gini
        mincomb = comb
        min_split1 = split1
        min_split2 = split2

    #print "%d, %d"%(len(split1), len(split2))

# **Question 8: How many possible splits are there of the ENTITY_TP feature?**

# In[60]:

len(combinations)
# (2**7 - 2) / 2 (because we optimize by throwing out half)

# **Question 9: Which split of ENTITY_TP best splits the Obama and Romney rows, as measured by the Gini Index?**

# In[61]:

# question 9
mincomb

# **Question 10: What is the Gini Index of this best split?**

# In[62]:

# question 10
mingini

# **Question 11: How much does this partitioning reduce the Gini Index over that of the overall data set?**

# In[63]:

# question 11
gini(df) - mingini

# **Question 12: How many Romney rows and Obama rows are in your first partition? How many Romney rows and Obama rows are in your second partition?**

# In[64]:

# question 12
print("Romney: %s, Obama: %s"%(sum(min_split1.CAND_ID == "Romney"), sum(min_split1.CAND_ID == "Obama")))
print("Romney: %s, Obama: %s"%(sum(min_split2.CAND_ID == "Romney"), sum(min_split2.CAND_ID == "Obama")))

# In this exercise, you will be partitioning the original dataset (as opposed to further partitioning the transaction amount partitions from the previous set of questions).
#
# Python tip: the combinations function of the itertools module allows you to enumerate combinations of a list

# ## Training a decision tree

# **Question 13: Using all of the features in the original dataframe read in at the top of this notebook, train a decision tree classifier that has a depth of three (including the root node and leaf nodes). What is the accuracy of this classifier on the training data?**

# In[65]:

from random import sample
def trainingSample(n, size):
    rows = sample(range(n), size)
    return rows

def separateRows(training_rows, data):
    """ Return (training set, prediction set) with n% of rows in training set"""
    training = data.ix[training_rows]
    prediction = data.drop(training_rows)
    return (training, prediction)

# In[66]:

from datetime import datetime
from sklearn import preprocessing
from sklearn.feature_extraction import DictVectorizer

classifier = DecisionTreeClassifier(criterion='gini', splitter='best', max_depth=3, min_samples_split=2, min_samples_leaf=1, max_features=None, random_state=None, min_density=None, compute_importances=None, max_leaf_nodes=None)
df_new = df.copy()
df_new.TRANSACTION_DT = df_new.TRANSACTION_DT.apply(lambda x: x if len(x) == 8 else "0" + x)
df_new.TRANSACTION_DT = df_new.TRANSACTION_DT.apply(lambda x: datetime.strptime(x, "%m%d%Y").toordinal())

CAND_ID = df_new.CAND_ID
X = df_new.drop("CAND_ID", axis = 1)

vec = DictVectorizer()
X = pd.DataFrame(vec.fit_transform(X.to_dict("records")).toarray())
X.columns = vec.get_feature_names()

train_size = 0.75
training_rows = trainingSample(X.shape[0], int(train_size * X.shape[0]))
train_rows, pred_rows = separateRows(training_rows, X)
train_Y, pred_Y = separateRows(training_rows, CAND_ID)

train_Y = train_Y == 'Obama'
train_Y = train_Y.astype(int)
pred_Y = pred_Y == 'Obama'
pred_Y = pred_Y.astype(int)

# In[67]:

clf = classifier.fit(train_rows, train_Y)

# In[68]:

classifier.score(train_rows, train_Y)

# In[69]:

classifier.score(pred_rows, pred_Y)

#

# **Question 14: Export your decision tree to graphviz. Please submit a png file of this graphic to bcourses. In your write-up, write down the interpretation of the rule at each node (for example, 'Root node: rows from state AL go the the left, rows from all other states go to the right. Left child of root node: ... etc**

# In[70]:

from sklearn.externals.six import StringIO
with open("obama_romney.dot", 'w') as f:
    f = sklearn.tree.export_graphviz(clf, out_file=f)

# In[71]:

print(train_rows.columns[2768])
print(train_rows.columns[7])
print(train_rows.columns[745])
print(train_rows.columns[706])
print(train_rows.columns[810])
print(train_rows.columns[2745])

# The topmost split depends on the date of the contribution. The next highest are whether a contribution was from Akron, and whether CMTE_ID equals C90011156 (which we think has to do with who the contributor is). The final row splits on whether CMTE_ID equals C00521013, whether the donor is an individual, and whether the state is North Carolina.

# **Question 15: For each of your leaf nodes, specify the percentage of Obama rows in that node (out of the total number of rows at that node).**

# In[72]:

print(3419 / (3079 + 3419))
print(135 / (0 + 135))
print(75 / (56 + 75))
print(68/ (1 + 68))
print(364 / (17 + 364))
print(9 / (7 + 9))

# See this notebook for the basics of training a decision tree in scikit-learn and exporting the outputs to view in graphviz: http://nbviewer.ipython.org/gist/tebarkley/b68c04d9b31e64ce6023
#
# Scikit-learn classifiers require class labels and features to be in numeric arrays. As such, you will need to turn your categorical features into numeric arrays using DictVectorizer. This is a helpful notebook for understanding how to do this: http://nbviewer.ipython.org/gist/sarguido/7423289. You can turn a pandas dataframe of features into a dictionary of the form needed by DictVectorizer by using df.to_dict('records'). Make sure you remove the class label first (in this case, CAND_ID). If you use the class label as a feature, your classifier will have a training accuracy of 100%! The example notebook link also shows how to turn your class labels into a numeric array using sklearn.preprocessing.LabelEncoder().
#
# We already did this for you at the top of the notebook, but before you convert your features into numeric arrays, you should always make sure they are of the correct type (ie zip code should be a string, not a float, because it is a categorical variable).
#
# Question 14 asks you to interpret the rules at each decision tree node using the graphviz output. The graphviz output looks cryptic (ie it might tell you that X[1014] < 0.5 is the best split for a particular node. To figure out what feature that corresponds to, use the .get_feature_names() function of your DictVectorizer object. If that returns something like 'CITY=PHOENIX', then you know that the left child of the node contains rows not in Phoenix ('CITY=PHOENIX' ==0) and the right child of the node contains rows in Phoenix ('CITY=PHOENIX' == 1).

# In[162]:


import pandas as pd

from IMLearn.learners import UnivariateGaussian, MultivariateGaussian
import numpy as np
import plotly.graph_objects as go
import plotly.io as pio
import plotly.express as px

pio.templates.default = "simple_white"
TRUE_EXPECTATION=10
TRUE_VARIANCE=1
NUM_SAMPLES= 1000
MULTY_EXPECTATION = np.array([0,0,4,0])
MULTY_SIGMA = np.array([[1,0.2,0,0.5], [0.2, 2, 0, 0], [0, 0, 1, 0], [0.5, 0, 0, 1]])

def test_univariate_gaussian():

    # Question 1 - Draw samples and print fitted model
    samples = np.random.normal(TRUE_EXPECTATION, TRUE_VARIANCE, NUM_SAMPLES)
    uni_gaussian=UnivariateGaussian()
    uni_gaussian.fit(samples)
    print('Estimated expectation an variance: (',uni_gaussian.mu_,',', uni_gaussian.var_,')')

    # Question 2 - Empirically showing sample mean is consistent

    def find_expectation(n_samples):
        return uni_gaussian.fit(samples[0:n_samples]).mu_

    samples_count = np.arange(10, NUM_SAMPLES + 10, 10)

    vec=np.vectorize(find_expectation)
    expectations_diff=abs(vec(samples_count)-TRUE_EXPECTATION)

    fig = px.scatter(x=samples_count, y=expectations_diff)
    fig.update_layout(title="The effect of the samples amount in the expectation accuracy",
                      xaxis_title="Samples size", yaxis_title="Absolute Expectation Distance")
    fig.show()

    # Question 3 - Plotting Empirical PDF of fitted model
    arr_pdfs = uni_gaussian.pdf(samples)
    fig2 = px.scatter(x=samples, y=arr_pdfs)
    fig2.update_layout(title="The PDFs values as function of the Sample values", xaxis_title="Sample Value",
                      yaxis_title="PDF Value")
    fig2.show()

def test_multivariate_gaussian():
    # Question 4 - Draw samples and print fitted model
    samples = np.random.multivariate_normal(MULTY_EXPECTATION, MULTY_SIGMA, NUM_SAMPLES)
    multy_gaussian = MultivariateGaussian()
    multy_gaussian.fit(samples)
    print("Estimated expectation: ",multy_gaussian.mu_)
    print("Estimated covariance: \n",multy_gaussian.cov_)

    # Question 5 - Likelihood evaluation
    array_f = np.linspace(-10, 10, 200)
    results = np.zeros(shape=(200,200))
    count_i = 0
    for f1 in array_f:
        count_j = 0
        for f3 in array_f:
            curr_mu = np.array([f1, 0, f3, 0])
            results[count_i][count_j] = MultivariateGaussian.log_likelihood(curr_mu,MULTY_SIGMA,samples)
            count_j+=1
        count_i+=1

    fig = go.Figure(go.Heatmap(x=array_f, y=array_f, z=results, colorbar=dict(title = "Log Likelihood")))
    fig.update_layout(title="The Log Likelihood Heatmap of the expectation [f1, 0, f3, 0]:",
                                                                xaxis_title ="f3 values",
                                                                yaxis_title ="f1 values")
    fig.show()

    # Question 6 - Maximum Likelihood
    max_ind = np.unravel_index(np.argmax(results), results.shape)
    print("The model achieved the maximum log likelihood is: [",round(array_f[max_ind[0]],3), ",0,",round(array_f[max_ind[1]],3),",0]")

if __name__ == '__main__':
     np.random.seed(0)
     test_univariate_gaussian()
     test_multivariate_gaussian()


import numpy as np
import equidistantPoints as p
import gaussianNoise as gn
import linearRegression as lr
import leastSquaresEstimate as ls
import meanSquareError as m
from random import random
from random import seed
import matplotlib.pyplot as plt
import math

'''
-------------------------------------------------------------------------------------------
Exercise 1:
-------------------------------------------------------------------------------------------
generalised regression model
y = theta0 + theta1 * x + theta2 * x^2 + theta3 * x^3 + theta5 * x^5 + noise

where noise corresponds to white Gaussian noise

and the components of the weight vector theta assume the values:
theta0 = 0.2
theta1 = -1
theta2 = 0.9
theta3 = 0.7
theta4 =  0
theta5 = -0.2

so:
theta = transposed [0.2 -1 0.9 0.7 -0.2]

In all cases of exercise 1 we consider N equidistant points x1,x2, ... xn n=1, 2, ... , N
all the points are in the interval [0,2]

our training set consists of
yn = theta0 + theta1 * xn + theta2 * xn^2 + theta3 * xn^3 + theta5 * xn^5 + noise_n n=1, 2, ... ,N

noise_n are noise samples coming from a Gaussian distribution with
mean=0, variance= sigma_n^2

--------------------------------------------------------------------------------------------
'''
'''
--------------------------------------------------------------------------------------------
For question 1.1:

Consider N=20, sigma_n^2=0.1
and
y = theta0 + theta1 * x + theta2 * x^2 + theta3 * x^3 + theta5 * x^5 + noise

1)Use Least Squares method to estimate the parameter vector
2)Calculate MSE of y over
a)the training set
b)a test set of 1000 points in the interval [0,2]
--------------------------------------------------------------------------------------------
'''

#initializing weight vector theta
theta = np.zeros((6,1))
thetaValues = [0.2, -1, 0.9, 0.7,  0, -0.2]

for i in range(0, 6):
    theta[i] = thetaValues[i]

thetaTransposed = np.transpose(theta)

#N=20 equidistant points in the interval [0,2]
N=20
start=0
end=2

X, Fx = p.getPoints(N,start,end,6)

#noise
variance=0.1
mean=0
noise = gn.getNoise(N,mean,variance)

#get the yn's
Y = lr.getY(N, X, noise, thetaTransposed).T
Y1 = lr.getYNonoise(N, X, thetaTransposed)

#there are more data points than there are parameters to be determined
thetaPredicted, Fx = ls.getY(N,Fx,Y,6)

#predicted y values
Y_pred = np.dot(thetaPredicted.T,Fx.T)

#mean square error over training set
MSE = m.MSE(Y_pred,Y,N)

print("theta LS estimation is: ",thetaPredicted.T)
print("the MSE for the train set is:", MSE)
#blue for actual values red for predicted
plt.figure(num=1)
plt.title("training set")
plt.plot(X,Y1[0],'bo',X,Y_pred[0],'ro')
plt.xlabel('X')
plt.ylabel('Y')
plt.show()

'''
----------------------------------------
#create the test set
----------------------------------------
'''
N=1000
# seed random number generator
seed(1)
# generate 1000 random numbers between 0-2
upper_bound = 2
lower_bound = 0
test_X=[]
for _ in range(1000):

    if lower_bound != 0:
        value = random()*(upper_bound-lower_bound)*lower_bound
    else:
        value = random()*upper_bound

    test_X.append(value)

test_X = np.array(test_X, dtype=float)
Fx = np.ones((N,6))
for i in range(6):
    for j in range(N):
        Fx[j,i] = math.pow(test_X[j], i)

#noise
noise = gn.getNoise(N,mean,variance)

#get the yn's
Y_test = lr.getY(N, test_X, noise, thetaTransposed).T
Y1 = lr.getYNonoise(N, test_X, thetaTransposed)

#predicted y values over the testing set
Y_pred_test = np.dot(thetaPredicted.T,Fx.T)

#mean square error over test set
MSE_test = m.MSE(Y_pred_test,Y_test,N)

print("the MSE for the test set is:", MSE_test)

#blue for actual values red for predicted
plt.figure(num=3)
plt.title("test set 1 ")
plt.plot(test_X,Y1[0],'bo',test_X,Y_pred_test[0],'ro')
plt.xlabel('X')
plt.ylabel('Y')
plt.show()


import numpy as np
from scipy.stats import wasserstein_distance
from sklearn.metrics import pairwise_distances
import ctypes
from functools import partial

def l2_dist(x, y):

    xx = np.sum(x**2, axis = 1)
    yy = np.sum(y**2, axis = 1)
    xy = np.dot(x, y.transpose((1,0)))

    d = xx[:,None] - 2*xy + yy[None,:]
    d[d<0] = 0
    d = (d)**(1/2)

    return d

def l1_dist(x, y):
    return np.sum(np.absolute(x[:,None,:]-y[None,:,:]), axis = 2)

def earth_mover_dist(x, y):
    return pairwise_distances(x, y, metric=wasserstein_distance)

def smart_dist(x, y, method="L2"):
    if method == "L1":
        return l1_dist(x, y)
    elif method == "L2":
        return l2_dist(x, y)
    elif method == "EM":
        return earth_mover_dist(x, y)
    else:
        return x


import numpy as np
from mpl_toolkits import mplot3d
import matplotlib.pyplot as plt
import sys

def get_normal_to_plane(points):
    imh, imw = points.shape
    mean = np.average(points, axis=0)
    R = points - mean
    w,v = np.linalg.eig(np.matmul(R.T,R))
    idx = np.argmin(w)
    normal = v[:,idx]
    return mean, normal

def plot_plane(points, normal, mean):
    fig = plt.figure()
    ax = plt.axes(projection="3d")
    ax.set_xlabel("X")
    ax.set_ylabel("Y")
    ax.set_zlabel("Z")
    ax.scatter(points[:,0], points[:,1], points[:,2], color='b')
    x = np.linspace(min(points[:,0]),max(points[:,0]),3)
    y = np.linspace(min(points[:,1]),max(points[:,1]),3)
    X,Y = np.meshgrid(x,y)
    Z = -(normal[0]/normal[2])*X -(normal[1]/normal[2])*Y + (np.dot(normal, mean)/normal[2])
    ax.plot_wireframe(X,Y,Z,color='r')
    plt.show()

def get_average_distanace(points, normal, mean):
    normal = normal/np.linalg.norm(normal)
    return np.mean(abs(np.dot(points-mean, normal)))

if __name__ == "__main__":
    filename = sys.argv[1]
    points = np.loadtxt(filename)
    mean, normal = get_normal_to_plane(points)
    plot_plane(points, normal, mean)
    avg_distance = get_average_distanace(points, normal, mean)
    print (normal, avg_distance, np.dot(normal,mean))


'''
Eduard Larrañaga
Computational Astrophysics
2020

Hermite Interpolation Method
'''

import numpy as np

#Hermite Coefficients
def psi0(z):
	'''
	------------------------------------------
	psi0(z)
	------------------------------------------
	Returns the Hermite coefficients Psi_0
	for the interpolation
	Receives as arguments: z
	------------------------------------------
	'''
	psi_0 = 2*z**3 - 3*z**2 + 1
	return psi_0

def psi1(z):
	'''
	------------------------------------------
	psi1(z)
	------------------------------------------
	Returns the Hermite coefficients Psi_1 for
	the interpolation
	Receives as arguments: z
	------------------------------------------
	'''
	psi_1 = z**3 - 2*z**2 + z
	return psi_1

# Interpolated Polynomial
def H3(x, xi, fi, dfidx):
	'''
	------------------------------------------
    H3(x, xi, fi, dfidx)
    ------------------------------------------
    Returns the values of the Cubic Hermite
    interpolated polynomial in a set of points
    defined by x
    x : array of points where the interpolated
    polynomial will be evaluated
    xi : array of 2 data points
    fi : array of values of the function at xi
    dfidx : array of values of the derivative
    of the function at xi
    ------------------------------------------
	'''
	# variable z in the interpolation
	z = (x - xi[0])/(xi[1] - x[0])

	h1 = psi0(z) * fi[0]
	h2 = psi0(1-z)*fi[1]
	h3 = psi1(z)*(xi[1] - xi[0])*dfidx[0]
	h4 = psi1(1-z)*(xi[1] - xi[0])*dfidx[1]
	H =  h1 + h2 + h3 - h4
	return H


import numpy as np

class GaussianNB:
    classes = None
    priors = None
    means = None
    variances = None

    def __init__(self):
        pass

    def fit(self, features, labels, priors_equal=True):
        self.classes = np.unique(labels)

        self.priors = np.ones(self.classes.shape) / self.classes.size

        self.means = {}
        self.variances = {}
        for i, clazz in enumerate(self.classes):
            positions = (labels == clazz).ravel()
            if not priors_equal:
                self.priors[i] = positions.sum() / labels.size
            self.means[clazz] = features[positions, :].mean(axis=0)
            self.variances[clazz] = features[positions, :].var(axis=0)

    def predict(self, features):
        likelihoods = np.zeros((features.shape[0], self.classes.size))
        for i, clazz in enumerate(self.classes):
            gaussians = np.exp(-np.square(features - self.means[clazz]) / (2 * self.variances[clazz])) \
                        / np.sqrt(2 * np.pi * self.variances[clazz])
            likelihoods[:, i] = np.product(gaussians, axis=1)

        posteriors = likelihoods * self.priors
        decisions = np.argmax(posteriors, axis=1)
        return self.classes[decisions]