"""This module contains a generic way to implement an iteration procedure and
other known interations using generic iteration function.
"""

import numpy as np

def generic_iteration(p0, iter_f, stop_f):
    """This functions implements a generic iterations. Repeat the given funcion
    while the stopping condition is not fulfilled.

    Parameters
    ---------
    p0 : float
        intial point of the iteration
    iter_f: function
        function which receives a number and return a number. Decides the next
        state of the system.
    stop_f: function
        function which receives a list of numbers and return a boolean.
        Decides the stoping condition.

    Returns
    -------
    sequence: np.ndarray
        the iterated sequence.

    TODO
    ----
    Processes with memory.
    """

    sequence = []
    p = p0
    complete = False
    while not complete:
        sequence.append(p)
        # Stop clause
        complete = stop_f(sequence)
        # Transformation
        p = iter_f(p)

    sequence = np.array(sequence)
    return sequence

def hasse_collatz(n):
    """Collatz conjeture is a conjeture that determine that the algorithm known
    as Hasse's algorithm always ends in 1.

    Parameters
    ----------
    n : int
        natural number

    Returns
    -------
    sequence: array_like
        path over the natural numbers walked by the algorithm.

    References
    ----------
    .. [1] https://en.wikipedia.org/wiki/Collatz_conjecture

    """

    iter_f = lambda x: x/2 if x % 2 == 0 else 3*x+1
    stop_f = lambda x: x[-1] == 1
    sequence = generic_iteration(n, iter_f, stop_f)
    return sequence

def iterator_x2minus1(p0, n):
    """Stable two cycle attractor.

    Parameters
    ----------
    p0: int
        the initial point.
    n: int
        the number of iterations we want to compute.

    Returns
    -------
    sequence: np.ndarray
        the iterated sequence.

    """
    iter_f = lambda x: x**2-1
    stop_f = lambda x: x[-1] == -1 and len(x) > n
    sequence = generic_iteration(p0, iter_f, stop_f)
    return sequence

def iterator_2xminus1(p0, n):
    """Chaotic iteration.

    Parameters
    ----------
    p0: int
        the initial point.
    n: int
        the number of iterations we want to compute.

    Returns
    -------
    sequence: np.ndarray
        the iterated sequence.

    """
    iter_f = lambda x: 2*x**2-1
    stop_f = lambda x: len(x) == n+1
    sequence = generic_iteration(p0, iter_f, stop_f)
    return sequence

def logistic_map(p0, n, r):
    """Logistic map iteration.

    Parameters
    ----------
    p0: int
        the initial point.
    n: int
        the number of iterations we want to compute.
    r: float
        the parameter of the logistic map. It is between 1 and 4.

    Returns
    -------
    sequence: np.ndarray
        the iterated sequence.

    """
    iter_f = lambda x: r*x*(1-x)
    stop_f = lambda x: len(x) == n+1
    sequence = generic_iteration(p0, iter_f, stop_f)
    return sequence

## Reformulate: two array iterations for the division
def division_iter(p0, n, div):
    """Division iteration.

    Parameters
    ----------
    p0: int
        the initial point.
    n: int
        the number of iterations we want to compute.
    div: int
        the number for which we want to divide the previous number.

    Returns
    -------
    sequence: np.ndarray
        the iterated sequence.

    """
    i_cond = lambda y: 10*y[0] if y[0] < div else y[0]
    iter_f = lambda x: np.array([int(i_cond(x)) % int(div),
                                 int(i_cond(x))/int(div)])
    stop_f = lambda x: x[-1][0] == 0 or len(x) == n+1
    sequence = generic_iteration(p0, iter_f, stop_f)[:, 1]
    return sequence


import numpy as np
import random
from sklearn.model_selection import StratifiedShuffleSplit

def divider():
    print('=' * 72)

def amostragem_aleatoria_simples(dataset, amostras):
    return dataset.sample(n=amostras, random_state=1)

def amostragem_sistematica(dataset, amostras):
    intervalo = len(dataset) // amostras
    random.seed(1)
    inicio = random.randint(0, intervalo)
    indices = np.arange(inicio, len(dataset), intervalo)
    amostra_sistematica = dataset.iloc[indices]
    return amostra_sistematica

def amostragem_agrupamento(dataset, numero_grupos):
    intervalo = len(dataset) // numero_grupos
    grupos = []
    id_grupo = contagem = 0
    for _ in dataset.iterrows():
        grupos.append(id_grupo)
        contagem += 1
        if contagem > intervalo:
            contagem = 0
            id_grupo += 1
    dataset['grupo'] = grupos
    random.seed(1)
    grupo_selecionado = random.randint(0, numero_grupos)
    return dataset[dataset['grupo'] == grupo_selecionado]

def amostragem_agrupamentoex1(dataset, numero_grupos):
    intervalo = len(dataset) // numero_grupos
    grupos = []
    id_grupo = contagem = 0
    for _ in dataset.iterrows():
        grupos.append(id_grupo)
        contagem += 1
        if contagem > intervalo:
            contagem = 0
            id_grupo += 1
    dataset['grupo'] = grupos
    random.seed(1)
    grupo_selecionado = random.randint(0, numero_grupos)
    return dataset[dataset['grupo'] == grupo_selecionado]

def amostragem_estratificada(dataset, amostras):
    percentual = amostras / len(dataset)
    divido = StratifiedShuffleSplit(test_size=percentual, random_state=1)
    for _, y in divido.split(dataset, dataset['income']):
        df_y = dataset.iloc[y]
    return df_y

def amostragem_estratificadae_ex1(dataset, amostras):
    percentual = amostras / len(dataset)
    divido = StratifiedShuffleSplit(test_size=percentual, random_state=1)
    for _, y in divido.split(dataset, dataset['c#default']):
        df_y = dataset.iloc[y]
    return df_y

def amostragem_reservatorio(dataset, amostras):
    stream = []
    # Cria uma lista de número do tamanho do dataset
    for c in range(len(dataset)):
        stream.append(c)
    i = 0
    tamanho = len(dataset)
    # Cria uma lista de 100 elementos 0
    reservatorio = [0] * amostras
    # Transforma em uma lista de 0 a 99
    for i in range(amostras):
        reservatorio[i] = stream[i]
    # Dá oportunidade para todos os elementos serem selecionados
    while i < tamanho:
        j = random.randrange(i + 1)
        if j < amostras:
            reservatorio[j] = stream[i]
        i += 1
    return dataset.iloc[reservatorio]


"""
Problem 3
"""

import numpy as np
from scipy.special import roots_legendre
from matplotlib import pyplot as plt

np.set_printoptions(precision=2)
ANS = 2*np.pi/3

def f(phi, z):
    return (z*np.sin(phi))**2

def sphere_integrate(func, m):
    phi_j = np.linspace(0, 2*np.pi, 2*m)
    z_i, w_i = roots_legendre(m)
    PHI, Z = np.meshgrid(phi_j, z_i)
    W = np.tile(w_i, (2*m, 1)).T
    return (np.pi/m)*np.sum(W * func(PHI, Z))

def test_sphere():
    m_vals = np.geomspace(3, 1000, dtype='i')
    I_vals = np.array([sphere_integrate(f, m) for m in m_vals])
    plt.figure()
    plt.subplot(111, xlabel=r'$m$ (number of points in $\theta$)', ylabel='Absolute error', xscale='log', yscale='log')
    plt.plot(m_vals, np.abs(I_vals - ANS))
    plt.show()

if __name__ == '__main__':
    print('COMMIT!!!')
    test_sphere()


"""
    Company Name : Uber

    Problem Statement :
        Given an array of integers, return a new array such that each element at index i of the new array is the product of all
        the numbers in the original array except the one at i.

        For example, if our input was [1, 2, 3, 4, 5], the expected output would be [120, 60, 40, 30, 24].
        If our input was [3, 2, 1], the expected output would be [2, 3, 6]

"""
import numpy as np

def solution(numbers:list):
    result = list(map(lambda x: np.prod(numbers[:x] + numbers[x+1:]), range(len(numbers))))
    return result

if __name__ == '__main__':
    print(solution(numbers=[1, 2, 3, 4, 5]))
    # [120, 60, 40, 30, 24]
    print(solution(numbers=[3,2,1]))
    # [2,3,6]


#!/usr/bin/env python

import numpy as np
import numpy.matlib
import sklearn.metrics
import torch
from torch.autograd import Variable
import torch.nn.functional as F

#	Written by Chieh Wu
#	This function calculates the Gaussian Kernel by approximate it through Random fourier Feature technique.

class RFF:
	# rff_width, the larger the better approximation
	def __init__(self, σ=1, rff_width=20000):
		self.σ = σ
		self.rff_width = rff_width
		self.phase_shift = None
		self.RFF_scale = np.sqrt(2.0/self.rff_width)

	def initialize_RFF(self, x, sigma, output_torch, dtype):
		self.x = x

		if self.phase_shift is not None: return
			#if x.shape[0] == self.N: return

		self.N = x.shape[0]
		self.d = x.shape[1]
		self.sigma = sigma

		if type(x) == torch.Tensor or type(x) == np.ndarray:
			self.phase_shift = 2*np.pi*np.random.rand(1, self.rff_width)
			#self.phase_shift = np.matlib.repmat(b, self.N, 1)
			self.rand_proj = np.random.randn(self.d, self.rff_width)/(self.sigma)
		else:
			raise ValueError('An unknown datatype is passed into get_rbf as %s'%str(type(x)))

		self.use_torch(output_torch, dtype)

	def use_torch(self, output_torch, dtype):
		if not output_torch: return

		dvc = self.x.device
		self.phase_shift = torch.from_numpy(self.phase_shift)
		self.phase_shift = Variable(self.phase_shift.type(dtype), requires_grad=False)
		self.phase_shift = self.phase_shift.to(dvc, non_blocking=True )										# make sure the data is stored in CPU or GPU device

		self.rand_proj = torch.from_numpy(self.rand_proj)
		self.rand_proj = Variable(self.rand_proj.type(dtype), requires_grad=False)
		self.rand_proj = self.rand_proj.to(dvc, non_blocking=True )										# make sure the data is stored in CPU or GPU device

	def __call__(self, x, dtype=torch.FloatTensor):
		self.initialize_RFF(x, self.σ, True, dtype)

		if type(self.x) == np.ndarray:
			self.x = torch.from_numpy(self.x)
			self.x = Variable(self.x.type(self.dtype), requires_grad=False)

		elif type(self.x) != torch.Tensor:
			raise ValueError('An unknown datatype is passed into get_rbf as %s'%str(type(self.x)))

		P = self.RFF_scale*torch.cos(torch.mm(self.x,self.rand_proj) + self.phase_shift)
		return P

	def torch_rbf(self, x, σ):
		P = self.__call__(x, σ)
		K = torch.mm(P, P.transpose(0,1))
		#K = (2.0/self.rff_width)*K
		K = F.relu(K)

		return K

	def np_feature_map(self, x):
		const = np.sqrt(2.0/self.rff_width)
		feature_map = const*np.cos(x.dot(self.rand_proj) + self.phase_shift)

		return feature_map

	def np_rbf(self):
		P = np.cos(self.x.dot(self.rand_proj) + self.phase_shift)
		K = (2.0/self.rff_width)*(P.dot(P.T))
		K = np.maximum(K, 0)
		K = np.minimum(K, 1)
		return K

	def get_rbf(self, x, sigma, output_torch=False, dtype=torch.FloatTensor):
		self.dtype = dtype
		self.initialize_RFF(x,sigma, output_torch, dtype)

		if output_torch: return self.torch_rbf(x, sigma)
		else: return self.np_rbf()

if __name__ == "__main__":
	np.set_printoptions(precision=4)
	np.set_printoptions(linewidth=300)
	np.set_printoptions(suppress=True)
	torch.set_printoptions(edgeitems=3)
	torch.set_printoptions(threshold=10_00)
	torch.set_printoptions(linewidth=400)

	X = np.random.randn(5,2)
	σ = 1
	gamma = 1.0/(2*σ*σ)

	rff = RFF(30000)
	rbf_np = rff.get_rbf(X, σ)

	rff_torch = RFF(30000)
	rbf_torch = rff_torch.get_rbf(torch.from_numpy(X).type(torch.FloatTensor), σ, True)

	rff_torch_inner = RFF(30000)
	φ = rff_torch_inner(torch.from_numpy(X).type(torch.FloatTensor), σ)
	rbf_torch_inner = torch.matmul(φ, φ.T)

	sk_rbf = sklearn.metrics.pairwise.rbf_kernel(X, gamma=gamma)

	print('Using SKlearn library')
	print(sk_rbf)
	print('\n')
	print('Using my own code with Pytorch')
	print(rbf_torch.cpu().detach().numpy())
	print('\n')
	print('Using my own code with Pytorch through Inner Product')
	print(rbf_torch_inner.cpu().detach().numpy())
	print('\n')
	print('Using My own code')
	print(rbf_np)


# -*- coding: utf-8 -*-
# @Author: TD21forever
# @Date:   2019-05-22 17:53:35
# @Last Modified by:   TD21forever
# @Last Modified time: 2019-05-22 20:37:39
import numpy as np

alist = np.array([[7,0,0,0,0],[3,8,0,0,0],[8,1,0,0,0],[2,7,4,4,0],[4,5,2,6,5]])
def solution(alist):
	rowNum = alist.shape[0]
	dp = np.zeros([rowNum,rowNum])
	for i in range(rowNum):
		dp[rowNum-1][i] = alist[rowNum-1][i]
	#从倒数第二行开始
	for i in range(rowNum-2,-1,-1):
		for j in range(i+1):
			dp[i][j] = max(dp[i+1][j],dp[i+1][j+1])+alist[i][j]
	return dp
res = solution(alist)[0][0]
print("原始三角形")
print(alist)
print("最优解")
print(res)
print("动态规划数组")
print(solution(alist))

算法设计与分析/实验/EX5.py

算法设计与分析/实验/EX5.py

算法设计与分析/实验/EX5.py


import numpy as np
#############################################################  mean, median, standard deviation #############################################################
numbers = [1,2,3,4,5]
np.mean(numbers)
np.median(numbers)
np.std(numbers)

############################################################# 1D and 2D array #############################################################
a = np.array([1, 2, 3])   # Create a rank 1 array
print(type(a))            # Prints "<class 'numpy.ndarray'>"
print(a.shape)            # Prints "(3,)"
print(a[0], a[1], a[2])   # Prints "1 2 3"
a[0] = 5                  # Change an element of the array
print(a)                  # Prints "[5, 2, 3]"

b = np.array([[1,2,3],[4,5,6]])    # Create a rank 2 array
print(b.shape)                     # Prints "(2, 3)"
print(b[0, 0], b[0, 1], b[1, 0])   # Prints "1 2 4"

# Create basic arrays
a = np.zeros((2,2))   # Create an array of all zeros
print(a)              # Prints "[[ 0.  0.]
                      #          [ 0.  0.]]"

b = np.ones((1,2))    # Create an array of all ones
print(b)              # Prints "[[ 1.  1.]]"

c = np.full((2,2), 7)  # Create a constant array
print(c)               # Prints "[[ 7.  7.]
                       #          [ 7.  7.]]"

d = np.eye(2)         # Create a 2x2 identity matrix
print(d)              # Prints "[[ 1.  0.]
                      #          [ 0.  1.]]"

e = np.random.random((2,2))  # Create an array filled with random values
print(e)                     # Might print "[[ 0.91940167  0.08143941]
                             #               [ 0.68744134  0.87236687]]"

############################################################# Array indexing and slicing #############################################################
###############
# Create the following rank 2 array with shape (3, 4)
# [[ 1  2  3  4]
#  [ 5  6  7  8]
#  [ 9 10 11 12]]
a = np.array([[1,2,3,4], [5,6,7,8], [9,10,11,12]])

# Use slicing to pull out the subarray consisting of the first 2 rows
# and columns 1 and 2; b is the following array of shape (2, 2):
# [[2 3]
#  [6 7]]
b = a[:2, 1:3]

# A slice of an array is a view into the same data, so modifying it
# will modify the original array.
print(a[0, 1])   # Prints "2"
b[0, 0] = 77     # b[0, 0] is the same piece of data as a[0, 1]
print(a[0, 1])   # Prints "77"

###############
# Create the following rank 2 array with shape (3, 4)
# [[ 1  2  3  4]
#  [ 5  6  7  8]
#  [ 9 10 11 12]]
a = np.array([[1,2,3,4], [5,6,7,8], [9,10,11,12]])

# Two ways of accessing the data in the middle row of the array.
# Mixing integer indexing with slices yields an array of lower rank,
# while using only slices yields an array of the same rank as the
# original array:
row_r1 = a[1, :]    # Rank 1 view of the second row of a
row_r2 = a[1:2, :]  # Rank 2 view of the second row of a
print(row_r1, row_r1.shape)  # Prints "[5 6 7 8] (4,)"
print(row_r2, row_r2.shape)  # Prints "[[5 6 7 8]] (1, 4)"

# We can make the same distinction when accessing columns of an array:
col_r1 = a[:, 1]
col_r2 = a[:, 1:2]
print(col_r1, col_r1.shape)  # Prints "[ 2  6 10] (3,)"
print(col_r2, col_r2.shape)  # Prints "[[ 2]
                             #          [ 6]
                             #          [10]] (3, 1)"

############### Integer array indexing
a = np.array([[1,2], [3, 4], [5, 6]])

# An example of integer array indexing.
# The returned array will have shape (3,) and
print(a[[0, 1, 2], [0, 1, 0]])  # Prints "[1 4 5]"

# The above example of integer array indexing is equivalent to this:
print(np.array([a[0, 0], a[1, 1], a[2, 0]]))  # Prints "[1 4 5]"

# When using integer array indexing, you can reuse the same
# element from the source array:
print(a[[0, 0], [1, 1]])  # Prints "[2 2]"

# Equivalent to the previous integer array indexing example
print(np.array([a[0, 1], a[0, 1]]))  # Prints "[2 2]"

################
# Create a new array from which we will select elements
a = np.array([[1,2,3], [4,5,6], [7,8,9], [10, 11, 12]])
print(a)  # prints "array([[ 1,  2,  3],
          #                [ 4,  5,  6],
          #                [ 7,  8,  9],
          #                [10, 11, 12]])"

# Create an array of indices
b = np.array([0, 2, 0, 1])

# Select one element from each row of a using the indices in b
print(a[np.arange(4), b])  # Prints "[ 1  6  7 11]"

# Mutate one element from each row of a using the indices in b
a[np.arange(4), b] += 10

print(a)  # prints "array([[11,  2,  3],
          #                [ 4,  5, 16],
          #                [17,  8,  9],
          #                [10, 21, 12]])

################ Boolean array indexing
a = np.array([[1,2], [3, 4], [5, 6]])

bool_idx = (a > 2)   # Find the elements of a that are bigger than 2;
                     # this returns a numpy array of Booleans of the same
                     # shape as a, where each slot of bool_idx tells
                     # whether that element of a is > 2.

print(bool_idx)      # Prints "[[False False]
                     #          [ True  True]
                     #          [ True  True]]"

# We use boolean array indexing to construct a rank 1 array
# consisting of the elements of a corresponding to the True values
# of bool_idx
print(a[bool_idx])  # Prints "[3 4 5 6]"

# We can do all of the above in a single concise statement:
print(a[a > 2])     # Prints "[3 4 5 6]"

############################################################# Data types #############################################################
x = np.array([1, 2])   # Let numpy choose the datatype
print(x.dtype)         # Prints "int64"

x = np.array([1.0, 2.0])   # Let numpy choose the datatype
print(x.dtype)             # Prints "float64"

x = np.array([1, 2], dtype=np.int64)   # Force a particular datatype
print(x.dtype)                         # Prints "int64"

x = np.array([[1,2],[3,4]], dtype=np.float64)
y = np.array([[5,6],[7,8]], dtype=np.float64)

############################################################# Array Math #############################################################
# Elementwise sum; both produce the array
# [[ 6.0  8.0]
#  [10.0 12.0]]
print(x + y)
print(np.add(x, y))

# Elementwise difference; both produce the array
# [[-4.0 -4.0]
#  [-4.0 -4.0]]
print(x - y)
print(np.subtract(x, y))

# Elementwise product; both produce the array
# [[ 5.0 12.0]
#  [21.0 32.0]]
print(x * y)
print(np.multiply(x, y))

# Elementwise division; both produce the array
# [[ 0.2         0.33333333]
#  [ 0.42857143  0.5       ]]
print(x / y)
print(np.divide(x, y))

# Elementwise square root; produces the array
# [[ 1.          1.41421356]
#  [ 1.73205081  2.        ]]
print(np.sqrt(x))

###############
x = np.array([[1,2],[3,4]])
y = np.array([[5,6],[7,8]])

v = np.array([9,10])
w = np.array([11, 12])

# Inner product of vectors; both produce 219
print(v.dot(w))
print(np.dot(v, w))

# Matrix / vector product; both produce the rank 1 array [29 67]
print(x.dot(v))
print(np.dot(x, v))

# Matrix / matrix product; both produce the rank 2 array
# [[19 22]
#  [43 50]]
print(x.dot(y))
print(np.dot(x, y))

##############################
x = np.array([[1,2],[3,4]])

print(np.sum(x))  # Compute sum of all elements; prints "10"
print(np.sum(x, axis=0))  # Compute sum of each column; prints "[4 6]"
print(np.sum(x, axis=1))  # Compute sum of each row; prints "[3 7]"

#############################
x = np.array([[1,2], [3,4]])
print(x)    # Prints "[[1 2]
            #          [3 4]]"
print(x.T)  # Prints "[[1 3]
            #          [2 4]]"

# Note that taking the transpose of a rank 1 array does nothing:
v = np.array([1,2,3])
print(v)    # Prints "[1 2 3]"
print(v.T)  # Prints "[1 2 3]"


'''
File: losses.py
Author: Roberto Vega
Description:
	This file contains common regularizers used in my functions.
'''
import numpy as np

class l2_reg():
	'''
	This class implements a simple l2 regularizer
	'''
	def __init__(self, alpha=0):
		'''
		Description:
			This function initializes the attributes of the l2 regularizer
		Args:
			alpha: Constant value that indicates the weight of the regularer.
		Returns:
			None
		'''

		self.alpha = alpha

	def compute_loss(self, theta):
		'''
		Description:
			This function computes alpha*sum(w^T w)
		Args:
			theta: Numpy array that contains the weights to be regularized
		Returns:
			reg_loss: Value of alpha*sum(w^T w)
		'''

		# Compute the square of every value
		sqr = np.square(theta)

		return self.alpha * np.sum(sqr)

	def compute_gradient(self, theta):
		'''
		Description:
			This function computes the gradient of the regularizer
		Args:
			theta: Numpy array that contains the weights to be regularized
		Returns:
			reg_loss: Value of the gradient of alpha*sum(w^T w)
		'''

		gradient = 2*self.alpha*theta

		return gradient

class l2_initial_weights_reg():
	'''
	This class implements a simple l2 regularizer on the difference between
	the initial weights and the current weights.
	'''
	def __init__(self, alpha=0, initial_weights=None):
		'''
		Description:
			This function initializes the attributes of the l2 regularizer
		Args:
			alpha: Constant value that indicates the weight of the regularer.
		Returns:
			None
		'''

		self.alpha = alpha
		self.init_w = initial_weights

	def compute_loss(self, theta):
		'''
		Description:
			This function computes alpha*sum(w^T w)
		Args:
			theta: Numpy array that contains the weights to be regularized
		Returns:
			reg_loss: Value of alpha*sum(w^T w)
		'''

		# Compute the square of every value
		sqr = np.square(theta-self.init_w)

		return self.alpha * np.sum(sqr)

	def compute_gradient(self, theta):
		'''
		Description:
			This function computes the gradient of the regularizer
		Args:
			theta: Numpy array that contains the weights to be regularized
		Returns:
			reg_loss: Value of the gradient of alpha*sum(w^T w)
		'''

		gradient = 2*self.alpha*(theta-self.init_w)

		return gradient


#create a rolling max and rollling min function given an array and window lenght input
#output for each function must be an array with same lenght with the max/min integer at each window lenght
#MUST: use numpy for speed

import numpy as np

def strided_app(a, L, S ):  # Window len = L, Stride len/stepsize = S
    nrows = ((a.size-L)//S)+1
    n = a.strides[0]
    return np.lib.stride_tricks.as_strided(a, shape=(nrows,L), strides=(S*n,n))

#Computes max given a window W and an array a
def max_filter1d_valid_strided(a, W):
    return strided_app(a, W, S=1).max(axis=1)

#Computes min given a window W and an array a
def min_filter1d_valid_strided(a, W):
    return strided_app(a, W, S=1).min(axis=1)

#Computes mean given a window W and an array a
def mean_filter1d_valid_strided(a, W):
    return strided_app(a, W, S=1).mean(axis=1)

def main():

    window = 3
    arr = np.array([1, 2, 30, 4, 5, 1, 5, 1, 5, 2, 0])

    max_arr = max_filter1d_valid_strided(arr, window)
    max_arr = np.insert(max_arr, 0, np.zeros(window-1))
    print(max_arr)

    min_arr = min_filter1d_valid_strided(arr, window)
    min_arr = np.insert(min_arr, 0, np.zeros(window-1))
    print(min_arr)

    mean_arr = mean_filter1d_valid_strided(arr, window)
    mean_arr = np.insert(mean_arr, 0, np.zeros(window-1))
    print(mean_arr)

if __name__ == "__main__":
    main()


"""
Author  : Enrique Posada Lozano
ID      : A01700711
ITESM Campus Qro
Artificial Intelligence Project
"""

"""
The following project employs the use of machine learning techniques made both from scratch and with the use of the scikit framework
in order to compare and contrast the performance in predictions between a linear regression and a decision tree, and also uses a decision tree for classification
As a side note, a random forest was also implemented yet the results of this are rather vague and therefore the decision tree is the "important" model for the project
"""

# Import required libraries
import sys
import numpy
import pandas
from sklearn.model_selection import train_test_split # For splitting dataset data
from sklearn.metrics import accuracy_score, confusion_matrix, classification_report, mean_squared_error, r2_score
import matplotlib.pyplot as plot
import time
# import seaborn

# import operator # helps in obtaining minimum and maximum in a list of tuples

# Classification libraries
# from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier, RandomForestRegressor
from sklearn import tree

# Linear regression library
from sklearn.linear_model import LinearRegression

# Global variables
totalErrorLinearRegression = []

# --------- Decision Tree Functions

def getMSE(y, mean):
    """
        Calculates the MSE (Mean Square Error) of the data given
        This is the impurity function used for regression trees

    """
    # print("\nMY Y\n",mean)
    if len(y) == 0: # Done in order to avoid obtaining nan values if there are no elements contained in y
        return 0
    mse = numpy.average((y-mean)**2)
    # print("mse\t",mse)
    return mse

# Entropy/Gini are the Cost Functions for Decision Trees (Classification)
def getEntropy(data):
    """
        Calculates the Entropy for a given labeled Data
        This is the impurity function for classification trees

        data    -> Data provided for labels
        It is used to obtain unique elements the make up a class and applies Shannon's entropy formula

        Formula is as follows
        np.sum([(-counts[i]/np.sum(counts))*np.log2(counts[i]/np.sum(counts)) for i in range(len(elements))])
    """
    _, elementsCounted = numpy.unique(data, return_counts = True) # Obtains data only for the indices provided using loc
    totalElements = numpy.sum(elementsCounted)
    acc = (elementsCounted/totalElements) * numpy.log2(elementsCounted/totalElements) # numpy makes this type of operation faster without the need of a for loop
    acc = - numpy.sum(acc) # axis=0 for rows
    # USED After a a new split is made
    return acc

def informationGain(systemEntropy, label, leftPartitionIndices, rightPartitionIndices):
    """
        Measures the quality of a split
        Acts as a reduction in noise

        systemEntropy           -> The entropy of the current Node in tree
        label                   -> The class values of the whole training dataset
        leftPartitionIndices    -> The obtained left indices obtained from the split
        rightPartitionIndices   -> The obtained right indices obtained from the split
    """

    leftLabels = label.loc[leftPartitionIndices]
    rightLabels = label.loc[rightPartitionIndices]
    # info gain
    # fi = Frequency of label at left branch
    leftFi = len(leftPartitionIndices) / (len(leftPartitionIndices) + len(rightPartitionIndices))
        # Calculate the entropy for the quality of the Split
    entropy = (leftFi * getEntropy(leftLabels)) + ((1 - leftFi) * getEntropy(rightLabels))
    # impurity = (leftFi * getEntropy(leftLabels)) + ((1 - leftFi) * getEntropy(rightLabels)))
    infoGain = systemEntropy - (entropy)
    # infoGain = systemEntropy - (impurity)
    joinedElementIndices = leftPartitionIndices.union(rightPartitionIndices)
    joinedElements = numpy.unique(label.loc[joinedElementIndices], return_counts = True)
    return infoGain, joinedElements

def splitData(data, column, value):
    """
        Splits the data given into left and right branches by comparing data greater than a given value and data less than given value
        Update, obtains only indices in order to be faster

        data    -> Dataset
        column  -> Current feature being evaluated
        value   -> Value provided to evaluate the dataset and return the indices of those
    """
    left = data[data[column] <= value]
    right = data[data[column] > value]
    return left.index,right.index

def findBestSplit(data, label, type):
    """
        Calculates and gets the Best Split for the tree
        returns a dictionary containing the best split found

        data    -> The data from which it should find the best split
        label   -> The class values for given data
        type    -> "regression" or "classification"
    """

    # variables containing information
    bestSplit = {}

    # Check for the best feature to make the split
    if type == "classification":
        systemEntropy = getEntropy(label) # Calculate the entropy for the system
        bestSplit = { "bestValue": None, "bestFeature": None, "entropy": systemEntropy, "bestInformationGain": 0, "bestClassValues":None, "bestLeftChildren": None, "bestRightChildren": None }

        # Original Implementation
        for feature in data:
            # print("Current Feature: ",feature)
            elements, _ = numpy.unique(data[feature], return_counts = True)
            # print("Unique elements\t",elements)
            # print(elementsCounted)
            for value in elements:
                leftPartitionIndices, rightPartitionIndices = splitData(data, feature, value) # Just like the Yes/No Excel Worksheet
                infoGain, classValues = informationGain(systemEntropy, label, leftPartitionIndices, rightPartitionIndices)
                if infoGain > bestSplit["bestInformationGain"]:
                    print("BestGain\t"+str(infoGain),end="\r")
                    bestSplit["bestFeature"] = feature
                    bestSplit["bestValue"] = value
                    bestSplit["bestInformationGain"] = infoGain
                    bestSplit["bestClassValues"] = classValues
                    # This saves the two branches made
                    bestSplit["bestLeftChildren"] = leftPartitionIndices
                    bestSplit["bestRightChildren"] = rightPartitionIndices
    else:
        bestSplit = { "bestFeature": None, "mse": 0, "value":0, "bestMSE": float('inf'), "bestLeftChildren": None, "bestRightChildren": None }

        bestSplit["value"] = numpy.average(label)

        for feature in data:
            elements, _ = numpy.unique(data[feature], return_counts = True)
            # print("CURRENT FEATURE\t",feature)
            for val in elements:
                leftPartitionIndices, rightPartitionIndices = splitData(data, feature, val) # Just like the Yes/No Excel Worksheet
                mseLeftRight = getMSE(label.loc[leftPartitionIndices.union(rightPartitionIndices)],numpy.average(label))
                leftMean = numpy.average(label.loc[leftPartitionIndices]) if len(leftPartitionIndices) > 0 else 0
                rightMean = numpy.average(label.loc[rightPartitionIndices]) if len(rightPartitionIndices) > 0 else 0

                leftFi = len(leftPartitionIndices) / (len(leftPartitionIndices) + len(rightPartitionIndices))

                # Calculate the quality of the Split
                branchesMSE = (leftFi * getMSE(label.loc[leftPartitionIndices],leftMean)) + ((1 - leftFi) * getMSE(label.loc[rightPartitionIndices],rightMean))
                # Condition for establishing the best split (the one with least MSE)
                if branchesMSE < bestSplit["bestMSE"]:
                    print("BEST MSE\t"+str(branchesMSE.round(5))+" and Label Value "+str(bestSplit["value"]), end="\r")
                    # print("Feature best\t",feature)
                    bestSplit["bestFeature"] = feature
                    bestSplit["bestValue"] = val
                    # bestSplit["value"] = numpy.average(label)
                    # bestSplit["bestMSE"] = branchesMSE
                    bestSplit["mse"] = mseLeftRight
                    bestSplit["bestMSE"] = branchesMSE
                    # bestSplit["bestClassValues"] = classValues
                    # This saves the two branches made
                    bestSplit["bestLeftChildren"] = leftPartitionIndices
                    bestSplit["bestRightChildren"] = rightPartitionIndices
    return bestSplit

# NOTE: The decision tree implementation is based on
    # https://machinelearningmastery.com/implement-decision-tree-algorithm-scratch-python/ and https://towardsdatascience.com/algorithms-from-scratch-decision-tree-1898d37b02e0
    # Visit for more information as this shows one how to build a decision tree from scratch
    # https://www.python-course.eu/Decision_Trees.php
def insertLeaf(samples,label):
    """
        Inserts a leaf node which consists of
        samples ->  Data contained for the amount of elements that were counted for a given class/label
        labels  ->  Data contained for the class
    """
    # print("A leaf has been added")
    labelValue = pandas.unique(label)
    return { "samples":len(samples), "class":labelValue[0], "elementsCounted":len(samples), "isLeaf": True }

def buildDecisionTreeClassifier(samples, y, max_depth, currentLevel=0):
    """
    Builds a Decision Tree for Classification
    If the best information gain is 0, then it inserts a leaf node, else it branches the tree out recursively
    """
    # print("current Level!\t",currentLevel)
    bestSplit = findBestSplit(samples,y,"classification")
    # print("\n")
    # print("Split\n"+str(bestSplit),end="\r")
    # If best gain is equal to 0, then it is a leaf node :)
    if bestSplit["bestInformationGain"] == 0 or currentLevel == max_depth:
        return insertLeaf(samples,y)
    # print("\n")
    # if not, creates more branches for the tree (Recursively)
    leftBranch = buildDecisionTreeClassifier(samples.loc[bestSplit["bestLeftChildren"]], y.loc[bestSplit["bestLeftChildren"]], max_depth, currentLevel+1)
    rightBranch = buildDecisionTreeClassifier(samples.loc[bestSplit["bestRightChildren"]], y.loc[bestSplit["bestRightChildren"]], max_depth, currentLevel+1)
    return { "feature": bestSplit["bestFeature"], "value": bestSplit["bestValue"], "infoGain": bestSplit["bestInformationGain"], "classValues": bestSplit["bestClassValues"], "leftBranch": leftBranch, "rightBranch": rightBranch, "isLeaf": False,  }

def buildDecisionTreeRegression(samples, y, max_depth, currentLevel=0):
    """
    Builds the Decision Tree
    Makes use of MSE
    If the impurity is 0, then it inserts a leaf node, else it branches the tree out recursively
    """
    # print("Current Level!\t",currentLevel,end="\r")
    # print("\n")
    bestSplit = findBestSplit(samples,y,"regression")
    # print("BEST SPLIT GREAT! ", bestSplit)
    # print("\n")
    # print("Current is TRUE?\t"+str(currentLevel >= max_depth)+"\n")
    # HERE YOU ALSO CHECK and declare the greatest level for insight purposes

    # If best gain is equal to 0, then it is a leaf node :)
    if bestSplit["mse"] == 0 or currentLevel == max_depth:
        return insertLeaf(samples,y)
    # if not, creates more branches for the tree (Recursively)
    leftBranch = buildDecisionTreeRegression(samples.loc[bestSplit["bestLeftChildren"]], y.loc[bestSplit["bestLeftChildren"]], max_depth, currentLevel+1)
    rightBranch = buildDecisionTreeRegression(samples.loc[bestSplit["bestRightChildren"]], y.loc[bestSplit["bestRightChildren"]], max_depth, currentLevel+1)
    return { "feature": bestSplit["bestFeature"], "value": bestSplit["bestValue"], "valAvg": bestSplit["value"], "mse": bestSplit["mse"], "bestMSE": bestSplit["bestMSE"],  "leftBranch": leftBranch, "rightBranch": rightBranch, "isLeaf": False,  }

def checkPredictions(instance, tree):
    """
        Traverses a given tree until a leaf is reached, returning the value to predict from a given instance
        instance    -> pandas tuple containing instance data
        tree        -> the decisionTree previously trained
    """
    # Check if it has currently reached a leaf node, and return the predicted label
    if tree["isLeaf"] == True:
        # print("CLASS IS!!!!\t", tree["class"])
        return tree["class"]

    # traverse left branch of tree
    # feature = tree["feature"]
    if instance[tree["feature"]] <= tree["value"]:
        return checkPredictions(instance, tree["leftBranch"])
    else:
        return checkPredictions(instance, tree["rightBranch"])
    return 1

def predict(samples, tree):
    """
        Predicts unseen data
        This is used for validating and testing the tree built
        This function is recursive since it consists on traversing the tree until a leaf node is reached, returning the class it belongs to

        data    -> Test Data or data from which we simply want to predict
        tree    -> The tree created to make tests on Data
    """
    # samples.apply(checkPredictions, axis=1,)
    predictions = list()
    for instance in range(len(samples)):
        pred = checkPredictions(samples.iloc[instance], tree)
        predictions.append(pred)
    # print("predictions\t",predictions)
    # print("MY TREE\t",tree)
    # print("Done")
    return predictions

def decisionTree(samples, y, type, max_depth=None):
    """
        This is the starting point to creating a tree and training the model
        samples -> Training Features
        y       -> Training Labels
        type    -> "regression" or "classification"
    """
    tree = {}
    print("MAX DEPTH ",max_depth)
    if max_depth != None and max_depth <= 0:
        print("\n\tError in Tree : max depth should be greater than 0")
        exit()
    if type == "classification":
        tree = buildDecisionTreeClassifier(samples, y, max_depth)
        return tree
    tree = buildDecisionTreeRegression(samples, y, max_depth)
    return tree

# ---- end ----

# ------------------------- Random Forest Implementation
def baggingPredictions(samples, forest):
    """
        Aggregates predictions and returns a list of the predicted values
        samples -> The sample of test features/instamces to predict
        forest  -> The forest created and to be used to predict
    """
    predictions = list()
    # Get all predictions in tree
    for tree in forest:
        predictions.append(predict(samples, tree))
    # Aggregate predictions
    predictions = numpy.array(predictions)
    forestPredictions = list()
    for row in predictions.T.tolist(): # transposes array and makes it a list so you have all predictions from each tree in a row
        forestPredictions.append(max(set(row),key=row.count))
    return forestPredictions
    # return predictions

def randomForest(samples, y, numTrees, type, max_depth=None):
    """
        # This forest implementation uses bagging and feature randomness when building each individual tree to try to create a forest of trees
        (NOTE : The performance did not provide better results, therefore it was better to stick with a "smart" tree since it requires more trees than 10 to work )

        # In theory, it is better to have many trees to guarantee it works than one tree that "does" it all
        # While some trees may be wrong, many other trees will be right

        samples     -> Training Features (Pandas DataFrame)
        y           -> Training Labels (Pandas DataFrame))
        numTrees    -> number of Trees to create the forest
        type        -> "classification" or "regression"
    """
    nRows = samples.shape[0] # Use all rows
    nFeatures = int(numpy.sqrt(samples.shape[1])) # Truncates float and converts it to an integer
    trees = list()
    for _ in range(numTrees):
        # SciKit considers the number of features to choose to be the Square Root of the column features
        sample = samples.sample(n=nRows,replace=False).sample(n=nFeatures,replace=False,axis=1) # replace True makes it endless to reach the next branch
        print("sample\n",sample)
        print("Y\n",y.loc[sample.index])
        # myTree = tree.DecisionTreeRegressor(criterion="mse")
        # myTree.fit(sample,y.loc[sample.index])
        myTree = decisionTree(sample,y.loc[sample.index],type,max_depth)
        print("TREE DONE!!!")
        trees.append(myTree)
    return trees

# ------------------------- Linear Regression Functions

def calculateHyp(params, sample):
    """
        Calculates the predicted value (hypothesis)
        yHat = a+bx1+cx2+ ... nxn   # Simplified Formula for Linear Regression

        yHat -> Predicted Value,    b0 -> Bias,     b1 -> Coefficient of 1st parameter ,    bn -> n coefficent
        x1 -> first input (feature -> ex: Age value),  xn -> n input/feature

        params -> Coefficientes of each parameter
        sample -> instance contained in the features dataset
    """
    acc = 0
    acc = params * sample # pandas takes care of multiplying each parameter with the respective feature
    # Optimized version
    acc = acc.sum(axis=1) # To sum all in row and not columns, axis is set to 1
    # print(acc)
    # exit()
    # acc = acc * (-1) # This is done for logistic regression
    return acc

def gradientDescent(params, samples, labels, learningRate):
    """
        Applies gradient descent in order to obtain new and "better" coefficient params (parameters)
        error = predictedValue - expectedValue

        params          ->
        samples         ->
        learningRate    ->
        labels          ->
    """
    error = 0
    newParams = list(params)

    # Optimized Version
    # acc = 0
    yHat = calculateHyp(params,samples)
    error = (yHat - labels)
    acc = numpy.dot(error,samples)
    newParams = params - (learningRate * ((1 / len(samples)) * acc)) # GD which in turn calculate the new params using alpha (learning rate)
    # print("newP",newParams)
    return newParams

def showErrors(params, samples, labels):
    """
    Calculates the error (Model Performance) (based on Benji's implementation)

    params  -> Coefficientes of each parameter
    samples -> All the training data
    y       -> All the real output data
    """
    # global totalError
    # error_acum = 0
    # error = 0

    # Optimized version
    hyp = calculateHyp(params,samples)
    # error = numpy.vectorize(crossEntropy)(hyp,y)
    # error = numpy.vectorize()

    mean_error = numpy.sqrt(getMSE(labels,hyp)) # cost function

    # print("MSE\t",mean_error)
    # test = (labels - hyp)**2
    # print("\nTest\n",test)
    # print("ANSWER\t",test.sum()/(len(samples)))
    # error_sum = error.sum() # REMOVED since getMSE already does this

    # PREVIOUS Implementation
    # for instance in range(len(samples)):
    #     hyp = calculateHyp(params,samples.iloc[instance])
    #     error = crossEntropy(hyp, y.iloc[instance])
    #     error_acum = error_acum + error # this error is different from the one used to update, this is general for each sentence it is not for each individual param
    # mean_error = error/len(samples)  # Removed since getMSE already does this
    totalErrorLinearRegression.append(mean_error) # Append to list showing error progress
    # print("DONE!!!")
    # exit()
    return mean_error

class CustomLinearRegression():
    """
        Custom Linear Regression
        Needs to be initiated with a given sample (x) and label (y), alongside a learning rate (alpha)
    """
    def __init__(self, samples, labels, alpha):
        self.labels = labels
        self.alpha = alpha # Learning Rate
        # Starting Coefficients/Weights (For each parameter)
        params = numpy.zeros(samples.shape[1]).tolist()
        # params = numpy.zeros(samples.shape[1])
        # Add a new column and param for the Bias
        # params.append(0) # Bias coefficient
        # Establish parameters starting value with random values
        params = numpy.random.rand(len(params))
        # self.samples = samples.assign(Bias=1)
        self.samples = samples
        self.params = params # Coefficients
        self.predictedValues = list() # list of predicted values

    def train(self):
        # Current epoch iteration
        epoch = 0
        start_time = time.time() # Keep track of current time passed
        # predicted_Values = []
        params = self.params
        # error = 0

        # While loop that stops until local minimum is reached or there is no further improvement in the bias
        while True:
            prevParams = list(params) # previous epoch coefficients
            params = gradientDescent(params,self.samples,self.labels, self.alpha)
            error = showErrors(params, self.samples, self.labels) # calculates the error between predicted and real data
            params = list(params) # In order to leave in same format as before -> not in a numpy array
            if(params == prevParams or epoch == 5000 or error < 5): # the loop will only end if no further changes are made/seen in the params, the number of epochs given is reached or a given minimum error is reached
                yHat = calculateHyp(params,self.samples)
                yHat = yHat.to_numpy()
                # predicted_Values = yHat
                self.predictedValues = yHat
                self.params = params
                # print("predicted values")
                # print(predicted_Values)
                    # print("Expected -> %.3f , Predicted Value -> %.3f [%d]" % (trainingLabel.iloc[instance], yhat, round(yhat)))
                print ("FINAL params :")
                print (params)
                print("THE TRAINING HAS FINISHED IN " + str(epoch) + " EPOCHS!!")
                finishedTrainingTime = time.time() - start_time
                print("The training lasted for " + str(finishedTrainingTime/60) + " minutes")
                break
            epoch += 1
            print("EPOCHS -> " + str(epoch) + " and error -> " + str(error), end="\r") # Overwrites the current line
        print("Training finished")
# ---- end ----

def scaleData(features):
    """
    Usually done for regressions
    Normalizes features in order for gradient descent to work correctly (improves the convergence speed of the logistic regression algorithm)
    features is an arg containing the sample of feature data to be normalized

    Normalization is made using Rescaling (min-max normalization) (https://en.wikipedia.org/wiki/Feature_scaling)

    returns the features of the dataset in a normalized manner and ( the minValues and maxValues for scaling data when a new query is made )

    normalizedVal = (x - min(x)) / (max(x) - min(x)))

    features -> The features to be normalized in the dataset
    """
    # print("\nMAX OF DATASET")
    # print(features.max())
    maxValues = features.max()
    # print("\nMIN OF DATASET")
    # print(features.min())
    minValues = features.min()
    print("\nInitializing Normalization ...")
    scaledFeatures = (features - minValues) / (maxValues - minValues)
    print("Finished Normalization\n")
    return scaledFeatures, minValues, maxValues

# Custom Query
def getCommonInput(dataframe):
    """
        Obtains the common input for making queries once models are trained

        Since what is chosen ranges from 0 to N, some options add a certain number in order to fit the original criteria of the dataset
            (Ex : Never, Sometimes and Always questions are added 1 since the dataset ranges the answer from 1 to 3)

        returns a pandas dataframe for use
    """
    dataframe["Gender"] = [int(input("\nAre you Male or Female?\n\t0 - Female\n\t1 - Male\n"))]
    dataframe["Age"] = [float(input("\nWhat is your Age?\n"))]
    dataframe["Height"] = [float(input("\nWhat is your Height?\n"))]
    dataframe["family_history_with_overweight"] = [int(input("\nHave you had past family members with obesity/overweight problems?\n\t0 - No\n\t1 - Yes\n"))]
    dataframe["FAVC"] = [int(input("\nDo you frequently eat high caloric food?\n\t0 - No\n\t1 - Yes\n"))]
    dataframe["FCVC"] = [1 + int(input("\nHow often do you eat vegetables with your meals?\n\t0 - Never\n\t1 - Sometimes\n\t2 - Always\n"))]
    dataframe["NCP"] = [1 + int(input("\nHow many main meals do you eat on a daily basis?\n\t0 - Between 1 and 2\n\t1 - 3 (Normal Quantity)\n\t2 - More than three meals\n"))]
    dataframe["SMOKE"] = [int(input("\nDo you smoke?\n\t0 - No\n\t1 - Yes\n"))]
    dataframe["TUE"] = [int(input("\nHow much time do you spend on electronic devices (videogames, computers, tv, phone) in a daily basis?\n\t0 - 0-2 hours\n\t1 - 3-5 hours\n\t2 - More than 5 hours\n"))]
    dataframe["FAF"] = [int(input("\nHow often do you exercise (do physical activity) on a weekly basis?\n\t0 - I do not exercise\n\t1 - 1-2 days\n\t2 - 2-4 days\n\t3 - 4-5 days (or more...)\n"))]
    dataframe["CH2O"] = [1 + int(input("\nHow much water do you drink daily?\n\t0 - Less than 1 liter\n\t1 - Between 1 and 2 liters\n\t2 - More than 2 liters\n"))]
    dataframe["SCC"] = [int(input("\nDo you monitor the amount of calories you eat on a daily basis?\n\t0 - No\n\t1 - Yes\n"))]

    # Multiple values in column - REQUIRE TO SEPARATE AS IN ONE HOT ENCODING
    _caec = int(input("\nDo you eat any food between meals?\n\t0 - No\n\t1 - Sometimes\n\t2 - Frequently\n\t3 - Always\n"))
    _calc = int(input("\nHow often do you drink alcohol?\n\t0 - I don't drink\n\t1 - Sometimes\n\t2 - Frequently\n\t3 - Always\n"))
    _mtrans = int(input("\nWhich way of transportation do you use on a daily basis?\n\t0 - Car\n\t1 - Motorbike\n\t2 - Bike\n\t3 - Public Transportation\n\t4 - Walking\n"))

    # Process columns that can have multiple values
    # caec
    dataframe['CAEC_Always'], dataframe['CAEC_Frequently'], dataframe['CAEC_Sometimes'], dataframe['CAEC_no'] = 0,0,0,0
    if _caec == 0:
        dataframe['CAEC_no'] = [1]
    elif _caec == 1:
        dataframe['CAEC_Sometimes'] = [1]
    elif _caec == 2:
        dataframe['CAEC_Frequently'] = [1]
    else:
        dataframe['CAEC_Always'] = [1]

    # calc
    dataframe['CALC_Always'], dataframe['CALC_Frequently'], dataframe['CALC_Sometimes'], dataframe['CALC_no'] = 0,0,0,0
    if _calc == 0:
        dataframe['CALC_no'] = [1]
    elif _calc == 1:
        dataframe['CALC_Sometimes'] = [1]
    elif _calc == 2:
        dataframe['CALC_Frequently'] = [1]
    else:
        dataframe['CALC_Always'] = [1]

    # mtrans
    dataframe['MTRANS_Automobile'], dataframe['MTRANS_Bike'], dataframe['MTRANS_Motorbike'], dataframe['MTRANS_Public_Transportation'], dataframe['MTRANS_Walking'] = 0,0,0,0,0
    if _mtrans == 0:
        dataframe["MTRANS_Automobile"] = [1]
    elif _mtrans == 1:
        dataframe["MTRANS_Motorbike"] = [1]
    elif _mtrans == 2:
        dataframe["MTRANS_Bike"] = [1]
    elif _mtrans == 3:
        dataframe["MTRANS_Walking"] = [1]
    elif _mtrans == 4:
        dataframe["MTRANS_Public_Transportation"] = [1]
    else:
        dataframe["MTRANS_Walking"] = [1]

    # At the end it returns a dataframe or numpy array ()
    return dataframe

def queryClassification():
    """
    Used for making classification queries using the models made. (Decision Tree by Hand and SCIKIT decision tree classifier implementation)
    Obtains several input for dataframe columns and calls each models predict function and shows the prediction from each one
    """
    print("Calculating Obesity Level with classification models\n")
    dataframe = getCommonInput(pandas.DataFrame({}, columns=classificationFeatures.columns))
    dataframe["Weight"] = float(input("\nWhat is your weight?\n"))
    print("Prediction Made")
    fwTreepred = treeClasif.predict(dataframe)
    if fwTreepred[0] == 0:
        fwTreepred = "Insufficient Weight"
    elif fwTreepred[0] == 1:
        fwTreepred = "Normal Weight"
    elif fwTreepred[0] == 2:
        fwTreepred = "Overweight Level I"
    elif fwTreepred[0] == 3:
        fwTreepred = "Overweight Level II"
    elif fwTreepred[0] == 4:
        fwTreepred = "Obesity Type I"
    elif fwTreepred[0] == 5:
        fwTreepred = "Obesity Type II"
    elif fwTreepred[0] == 6:
        fwTreepred = "Obesity Type III"

    treePred = predict(dataframe, classificationTree)
    if treePred[0] == 0:
        treePred = "Insufficient Weight"
    elif treePred[0] == 1:
        treePred = "Normal Weight"
    elif treePred[0] == 2:
        treePred = "Overweight Level I"
    elif treePred[0] == 3:
        treePred = "Overweight Level II"
    elif treePred[0] == 4:
        treePred = "Obesity Type I"
    elif treePred[0] == 5:
        treePred = "Obesity Type II"
    elif treePred[0] == 6:
        treePred = "Obesity Type III"
    return print("Predicted Classification Tree -> %s , Predicted SCIKIT Classification Tree -> %s" % (treePred, fwTreepred))

def queryRegression():
    """
    Used for making regression queries using the models made. (Linear Regression by Hand, SCIKIT Linear Regression implementation, Decision Tree by Hand and SCIKIT decision tree regressor implementation)
    Obtains several input for dataframe columns and calls each models predict function and shows the prediction from each one
    """
    print("Calculating weight with regression models\n")
    dataframe = getCommonInput(pandas.DataFrame({}, columns=regressionFeatures.columns))
    dataframe['NObeyesdad_Insufficient_Weight'], dataframe['NObeyesdad_Normal_Weight'], dataframe['NObeyesdad_Obesity_Type_I'], dataframe['NObeyesdad_Obesity_Type_II'], dataframe['NObeyesdad_Obesity_Type_III'], dataframe['NObeyesdad_Overweight_Level_I'], dataframe['NObeyesdad_Overweight_Level_II'] = 0,0,0,0,0,0,0
    _nobeyesdad = int(input("\nSelect a weight category to which the person relates to :\n\t0 - Insufficient Weight\n\t1 - Normal Weight\n\t2 - Overweight Type I\n\t3 - Overweight Type II\n\t4 - Obsesity Type I\n\t5 - Obsesity Type II\n\t6 - Obsesity Type III\n"))
    if _nobeyesdad == 0:
        dataframe['NObeyesdad_Insufficient_Weight'] = [1]
    elif _nobeyesdad == 1:
        dataframe['NObeyesdad_Normal_Weight'] = [1]
    elif _nobeyesdad == 2:
        dataframe['NObeyesdad_Overweight_Level_I'] = [1]
    elif _nobeyesdad == 3:
        dataframe['NObeyesdad_Overweight_Level_II'] = [1]
    elif _nobeyesdad == 4:
        dataframe['NObeyesdad_Obesity_Type_I'] = [1]
    elif _nobeyesdad == 5:
        dataframe['NObeyesdad_Obesity_Type_II'] = [1]
    else:
        dataframe['NObeyesdad_Obesity_Type_III'] = [1]

    # ScaleValues
    dataframe[['Age','Height','FCVC','NCP','CH2O','FAF','TUE']] = (dataframe[['Age','Height','FCVC','NCP','CH2O','FAF','TUE']] - regressionMinValues) / (regressionMaxValues - regressionMinValues)
    print("Prediction Made")
    fwTreepred = fwregressionTree.predict(dataframe)
    fwLRpred = lin.predict(dataframe)
    lrPred = calculateHyp(regressionModel.params, dataframe)
    treePred = predict(dataframe, regressionTree)
    return print("Predicted LR Value -> %.3f , Predicted SCIKIT LR Value -> %.3f , Predicted Regression Tree -> %.3f , Predicted SCIKIT Regression Tree -> %.3f" % (lrPred[0], fwLRpred[0], treePred[0], fwTreepred[0]))

# ---- end ----

# Obtain data
csvFileName = "./ObesityDataSet_raw_and_data_sinthetic/ObesityDataSet_raw_and_data_sinthetic.csv"

dataset = pandas.read_csv(csvFileName)

"""
STEP:
    Preprocess data
"""
# One of the most important use of feature engineering is that it reduces overfitting and improves the accuracy of a model.

# Classification Pre-Processing
    # Single value in column (Yes/No, Male/Female)
gender = pandas.get_dummies(dataset["Gender"]).drop("Female", axis=1) # Use Gender to convert to 1's or 0's and drop one of the columns since it is unnecesary
familyHistory = pandas.get_dummies(dataset["family_history_with_overweight"]).drop("no",axis=1)
favc = pandas.get_dummies(dataset["FAVC"]).drop("no",axis=1)
smoke = pandas.get_dummies(dataset["SMOKE"]).drop("no",axis=1)
scc = pandas.get_dummies(dataset["SCC"]).drop("no",axis=1)
    # NOTE: Most of these drop an unnessary column since it is not really needed at the time of processing data

    # Multiple values in column
caec = pandas.get_dummies(dataset["CAEC"], prefix="CAEC")
calc = pandas.get_dummies(dataset["CALC"], prefix="CALC")
mtrans = pandas.get_dummies(dataset["MTRANS"], prefix="MTRANS")

classificationDataset = dataset.drop(["Gender", "family_history_with_overweight", "FAVC", "CAEC", "SMOKE", "SCC", "CALC", "MTRANS", "NObeyesdad"], axis=1)
# Round continous values that do not require many unique values with decimal places (Ex: Age with decimals or Height with many decimals)
    # This does not help the values since the dataset was preprocessed before hand

# Add new encoded values
classificationDataset["Gender"], classificationDataset["family_history_with_overweight"], classificationDataset["FAVC"], classificationDataset["SMOKE"], classificationDataset["SCC"]  = [gender, familyHistory, favc, smoke, scc]
classificationDataset = pandas.concat([classificationDataset,caec,calc,mtrans], axis=1)

# Label handling
    # In this case, the label is treated with a label encoding technique, in order to avoid more features and make it one column for the model to understand
    # There are 7 types of Obesity, therefore labeling will be done from 0 to 6 for the model to understand
    # NOTE: In a Logistic Regression, one hit encoding is needed since the model needs to know for each one if the option is needed or not
# factorizedLabel = pandas.factorize(dataset["NObeyesdad"])
# classificationDataset["NObeyesdad"] = factorizedLabel[0]
factorizedLabel = (dataset["NObeyesdad"].replace({"Insufficient_Weight":0,"Normal_Weight":1,"Overweight_Level_I":2,"Overweight_Level_II":3,"Obesity_Type_I":4,"Obesity_Type_II":5,"Obesity_Type_III":6}).to_numpy(),pandas.Index(["Insufficient_Weight","Normal_Weight","Overweight_Level_I","Overweight_Level_II","Obesity_Type_I","Obesity_Type_II","Obesity_Type_III"]))
classificationDataset["NObeyesdad"] = factorizedLabel[0]
# print(factorizedLabel)

# NOTE: Now making them ordinal (with order) to check if there is a better performance from the model

# print(scaleData(forestDataset))
print("I think we finished checking the model preprocess data")

# Get Features and Labels for Classification
classificationFeatures = pandas.DataFrame(classificationDataset, columns = [
    'Age', 'Height', 'Weight', 'FCVC', 'NCP', 'CH2O', 'FAF', 'TUE',
    'Gender', 'family_history_with_overweight', 'FAVC', 'SMOKE', 'SCC',
    'CAEC_Always', 'CAEC_Frequently', 'CAEC_Sometimes', 'CAEC_no',
    'CALC_Always', 'CALC_Frequently', 'CALC_Sometimes', 'CALC_no',
    'MTRANS_Automobile', 'MTRANS_Bike', 'MTRANS_Motorbike',
    'MTRANS_Public_Transportation', 'MTRANS_Walking'
])
classificationLabels = classificationDataset["NObeyesdad"]
# Split the Dataset into a training dataset and a test dataset
    # In this case, the model is trained with 75% (3/4 of data) of the data given
        # (NOTE: Since no random_state is given, the seed by default is random for each time the code is executed)
classificationTrainingFeatures, classificationTestFeatures, classificationTrainingLabels, classificationTestLabels = train_test_split(classificationFeatures, classificationLabels, test_size=0.25)

# Regression Pre-Processing
nObeyesdad = pandas.get_dummies(dataset["NObeyesdad"], prefix="NObeyesdad") # Uses One Hot Encoding for the previous label/class
# Drop original columns
regressionDataset = dataset.drop(["Gender", "Weight", "family_history_with_overweight", "FAVC", "CAEC", "SMOKE", "SCC", "CALC", "MTRANS", "NObeyesdad"], axis=1)
regressionDataset, regressionMinValues, regressionMaxValues = scaleData(regressionDataset) # scale data ("REALLY ESSENTIAL!!!!!!! since it brings weights infinity if not")
# Scale for regression

# Insert dataframe with single column with the new values
regressionDataset["Gender"], regressionDataset["family_history_with_overweight"], regressionDataset["FAVC"], regressionDataset["SMOKE"], regressionDataset["SCC"]  = [gender, familyHistory, favc, smoke, scc]
# Insert dataframes with mutliple columns
regressionDataset = pandas.concat([regressionDataset, caec, calc, mtrans, nObeyesdad], axis=1)
# Insertion of Bias (For this project it did not really help)
# regressionDataset["Bias"] = 1

regressionFeatures = pandas.DataFrame(regressionDataset, columns=[
    'Age', 'Height', 'FCVC', 'NCP', 'CH2O', 'FAF', 'TUE', 'Gender', 'family_history_with_overweight', 'FAVC', 'SMOKE',
    'SCC', 'CAEC_Always', 'CAEC_Frequently', 'CAEC_Sometimes', 'CAEC_no', 'CALC_Always', 'CALC_Frequently', 'CALC_Sometimes', 'CALC_no',
    'MTRANS_Automobile', 'MTRANS_Bike', 'MTRANS_Motorbike', 'MTRANS_Public_Transportation', 'MTRANS_Walking' ,
    'NObeyesdad_Insufficient_Weight', 'NObeyesdad_Normal_Weight',
    'NObeyesdad_Obesity_Type_I', 'NObeyesdad_Obesity_Type_II',
    'NObeyesdad_Obesity_Type_III', 'NObeyesdad_Overweight_Level_I',
    'NObeyesdad_Overweight_Level_II'
])
regressionLabels = dataset["Weight"]

regressionTrainingFeatures, regressionTestFeatures, regressionTrainingLabel, regressionTestLabel = train_test_split(regressionFeatures, regressionLabels, test_size=0.25)

print("Finished pre-processing")

# queryClassification()
# queryRegression()

# ------------------------------------
# Decision Tree Classifier

realStart = time.time()

# SCIKIT
treeClasif = tree.DecisionTreeClassifier(criterion="entropy")
treeClasif.fit(classificationTrainingFeatures, classificationTrainingLabels)

# rf = randomForest(classificationTrainingFeatures, classificationTrainingLabels, 5, "classification")
# print(rf)

# Small version of the tree
fig, axes = plot.subplots(nrows = 1,ncols = 1,figsize = (4,4), dpi=300)
tree.plot_tree(treeClasif,feature_names=classificationFeatures.columns,class_names=factorizedLabel[1],filled = True)
fig.savefig('clasifTreeSmall.png')

print("Visualizing tree")
# fig = plot.figure(figsize=(200,125)) # 250,200
# _ = tree.plot_tree(treeClasif,
#                    feature_names=classificationFeatures.columns,
#                    class_names=factorizedLabel[1],
#                    filled=True,
#                    fontsize=24)
# fig.savefig('proHighFinal.png')
y_predTree = treeClasif.predict(classificationTestFeatures)
cm = confusion_matrix(classificationTestLabels, y_predTree)
print(cm)
plot.matshow(cm)
plot.title('SCIKIT Estimation of "Obesity" Level')
plot.colorbar()
plot.ylabel('Real Value')
plot.xlabel('Predicted Value')
plot.show()

# My implementation of a decision tree
classificationTree = decisionTree(classificationTrainingFeatures, classificationTrainingLabels, "classification")
# print("Classfification Tree\n",classificationTree)

pred = predict(classificationTestFeatures, classificationTree)
print("SCIKIT Decision Tree Classifier Accuracy : "+str(accuracy_score(classificationTestLabels, y_predTree))+" %")
print("SCIKIT's model has " + str(cm.diagonal().sum()) + " correct predictions and " + str(classificationTestFeatures.shape[0]-cm.diagonal().sum()) + " are wrong out of " + str(classificationTestLabels.shape[0]))

cm = confusion_matrix(classificationTestLabels, pred)
print(cm)
correctPredictions = cm.diagonal().sum()
plot.matshow(cm)
plot.title('"Obesity" Level')
plot.colorbar()
plot.ylabel('Real Value')
plot.xlabel('Predicted Value')
plot.show()

print ("\nAccuracy of Decision Tree Model with Test Data (%) : ", accuracy_score(classificationTestLabels, pred))
print("Decision Tree has " + str(correctPredictions) + " correct predictions and " + str(classificationTestFeatures.shape[0]-correctPredictions) + " are wrong out of " + str(classificationTestLabels.shape[0]))

# Regression

# Framework implementation of Linear Regression
lin = LinearRegression()
lin.fit(regressionTrainingFeatures, regressionTrainingLabel)

# # Make predictions with test features
lrpred = lin.predict(regressionTestFeatures)
print("RMSE SCIKIT Linear Regression : "+str(numpy.sqrt(mean_squared_error(regressionTestLabel, lrpred))))
print("R Squared SCIKIT Linear Regression :\t",r2_score(regressionTestLabel, lrpred)) # measures how close the data is to the fitted line as a percentage

# --------- Linear Regression ------------

# Learning Rate for GD
alpha = 0.305
totalErrorLinearRegression = []

regressionModel = CustomLinearRegression(regressionTrainingFeatures, regressionTrainingLabel, alpha)
regressionModel.train()
plot.plot(totalErrorLinearRegression)
plot.title("Error")
plot.xlabel("# Epochs")
plot.ylabel("Error (RMSE)")
plot.show()

yhat = calculateHyp(regressionModel.params,regressionTestFeatures)

print("RMSE Linear Regression : "+str(numpy.sqrt(mean_squared_error(regressionTestLabel, yhat))))
print("R Squared Linear Regression :\t",r2_score(regressionTestLabel, yhat)) # measures how close the data is to the fitted line as a percentage

# for instance in range(len(regressionTestFeatures)):
#     print("Expected -> %.3f , Predicted Value -> %.3f " % (regressionTestLabel.iloc[instance], yhat.iloc[instance]))

# Decision Tree Regressor
# Framework
fwregressionTree = tree.DecisionTreeRegressor(criterion="mse", max_depth=10)
fwregressionTree.fit(regressionTrainingFeatures, regressionTrainingLabel)
newpred = fwregressionTree.predict(regressionTestFeatures)
print("RMSE SCIKIT Decision Tree Regressor : "+str(numpy.sqrt(mean_squared_error(regressionTestLabel, newpred))))
print("R Squared SCIKIT Tree Regressor :\t",r2_score(regressionTestLabel, newpred)) # measures how close the data is to the fitted line as a percentage
print(fwregressionTree.get_depth())
print("Visualizing tree")
# Small version of tree
fig, axes = plot.subplots(nrows = 1,ncols = 1,figsize = (4,4), dpi=300)
tree.plot_tree(fwregressionTree,
            feature_names = regressionFeatures.columns,
            class_names=pandas.unique(regressionLabels),
            filled = True)
fig.savefig('regTreeSmall.png')

plot.show()

# fig = plot.figure(figsize=(200,125))
# _ = tree.plot_tree(regressionTree,
#                    feature_names=regressionFeatures.columns,
#                 #    class_names=pandas.unique(regressionLabels),
#                    filled=True,
#                    fontsize=24)
# fig.savefig('treeRegFinalDepth.png')

# Hand implementation
# Comparing both models
start = time.time()
regressionTree = decisionTree(regressionTrainingFeatures, regressionTrainingLabel, "regression", 10)
finishedTrainingTime = time.time() - start
print("\n\nThe training lasted for " + str(finishedTrainingTime/60) + " minutes")
print("regression Tree finished!!!!!!\n",regressionTree)
predRegTree = predict(regressionTestFeatures, regressionTree)
print("RMSE Regression Tree : "+str(numpy.sqrt(mean_squared_error(regressionTestLabel, predRegTree))))
print("R Squared Tree Regression :\t",r2_score(regressionTestLabel, predRegTree)) # measures how close the data is to the fitted line as a percentage

for instance in range(len(regressionTestFeatures)):
    print("Expected -> %.3f , Predicted LR Value -> %.3f , Predicted SCIKIT LR Value -> %.3f , Predicted Regression Tree -> %.3f , Predicted SCIKIT Regression Tree -> %.3f" % (regressionTestLabel.iloc[instance], yhat.iloc[instance], lrpred[instance], predRegTree[instance], newpred[instance]))

print("\nWhole program took "+str((time.time() - realStart)/60)+" minutes")


#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Grégoire Allaire, Flore Nabet et Thomas Wick
# Ecole Polytechnique
# MAP 411
# Hiver 2017/2018

# Résolution numérique de l'équation de transport
# u,t + v u,x = 0
# schéma explicite centré
########################################################
# Load packages
import numpy as np
import matplotlib.pyplot as plt

import pylab
import scipy as sp
from scipy import sparse
from scipy.sparse import linalg

########################################################
# Paramètres du probleme
v = 1.0        # v = vitesse
lg = 10.        # intervalle en x=[-lg,lg]
nx = 201       # nombre de points du maillage
dx = (2*lg)/(nx-1)       # dx = pas d'espace
cfl = 0.9     # cfl = dt/dx
dt = dx*cfl/v # dt = pas de temps
Tfinal = 2.   # Temps final souhaité

print("parametres de la discretisation : domaine (", -lg , lg , ") discretise avec nx=", nx, " points de discretisation et une taille de maille dx=", dx)

x = np.linspace(-lg,lg,nx)

# Initialize u0
u0 = np.zeros(len(x))
#print(len(u0))

# Set specific u0 values (same as in the scilab program)
for i in range (len(x)):
    if (1.0 - x[i]**2) < 0:
       u0[i] = 0
    else:
       u0[i] = 1.0 - x[i]**2 # donnée initiale

## Plot initial condition and save it to a file
plt.figure(1)
plt.clf()
plt.plot(x,u0) # trace u0 en fonction de x
plt.title('condition initiale, transport')
plt.xlabel('x')
plt.ylabel('u(x)')
#plt.savefig('u0.png')
plt.show()
plt.pause(1.)

########################################################
# Schéma numérique

# Initialize u by the initial data u0
u = u0.copy() # il faut faire une copie sinon on va changer u0 en changeant u

# Construction de la matrice (creuse) pour le schema explicite
# u(-10,t)=u(10,t)=0 on peut donc enlever ces deux coordonnees du systeme
#u_j^{n+1}=nu*cfl*u_{j-1}^n+(1-2*nu*cfl)u_j^n+nu*cfl*u_{j+1}^n
A=sp.sparse.diags([v*dt/(2*dx), 1., -v*dt/(2*dx)], [-1, 0, 1], shape=(nx-2, nx-2))

# Nombre de pas de temps effectues
nt = int(Tfinal/dt)
Tfinal = nt*dt # on corrige le temps final (si Tfinal/dt n'est pas entier)

# Time loop
for n in range(1,nt+1):

  # Schéma explicite centré
    u[1:len(u)-1]=A*u[1:len(u)-1]

 # Print solution
    if n%2 == 0:
      plt.figure(1)
      plt.clf()
      plt.plot(x,u0,'b',x,u,'r')
      plt.xlabel('$x$')
      plt.title('Schema explicite centre, $t=$%s' %(n*dt))
      plt.pause(0.1)

####################################################################
# comparaison solution exacte avec solution numerique au temps final

uexacte = np.zeros(len(u))

# on calcule la solution exacte qui est la donnée initiale transportée
for i in range(int(nx)):
    j = i - int(Tfinal*v/dx) ;
    j = max(j,1)
    uexacte[i] = u0[j]

plt.figure(2)
plt.clf()
plt.suptitle('Transport: comparaison au temps Tfinal$=$%s' %(Tfinal))
#plt.suptitle('Comparaison entre la solution exacte et la solution numerique au temps final $T=$ %s', %(Tfinal))
plt.plot(x,u0,'b',x,u,'or',x,uexacte,'k')
plt.legend(['Donnee initiale','Schema explicite centre','Solution exacte'],loc='best')
plt.show()


import argparse
import numpy as np
import pandas
import sklearn.linear_model
import utils

parser = argparse.ArgumentParser()
parser.add_argument("data_path_fit")
parser.add_argument("data_path_extrapolation")
utils.add_arguments(parser, ["output"])
args = parser.parse_args()
data_path_fit = args.data_path_fit
data_path_extrapolation = args.data_path_extrapolation
out_path = args.output

n_units_extrapolation = 1000
n_units_fit_max = 45
n_units_fit_min = 2

df_fit = pandas.read_csv(data_path_fit)
df_extrapolation = pandas.read_csv(data_path_extrapolation)

# Get the data points to fit and extrapolate on
n_units_values_fit = np.sort(df_fit["n_units"].unique())
n_units_values_fit = n_units_values_fit[(
    (n_units_values_fit <= n_units_fit_max)
    & (n_units_values_fit >= n_units_fit_min))]
n_values_fit = len(n_units_values_fit)
performances_fit = np.empty((n_values_fit))
for i, n_units in enumerate(n_units_values_fit):
    performances_fit[i] = df_fit[df_fit["n_units"] == n_units]["mean_performance"].iloc[0]
performance_extrapolation_measured = df_extrapolation[df_extrapolation["n_units"] == n_units_extrapolation]["mean_performance"].iloc[0]

# We model the performance as a function of the number of units as the power law function:
# 100 - performance = c * (1 / N) ^ alpha
# By taking the logarithm, we obtain the linear model:
# log(100 - performance) = a + b log(N), where a = log(c) and b = -alpha
# We therefore fit the parameters a and b using a linear regression
# from the log number of units, log(N), to the log error, log(100 - performance).

log_nunits_fit = np.log(n_units_values_fit)
log_nunits_extrapolation = np.log(n_units_extrapolation)
log_errors_fit = np.log(100 - performances_fit)

linear_model = sklearn.linear_model.LinearRegression(fit_intercept=True)
linear_model.fit(log_nunits_fit[:, np.newaxis], log_errors_fit)
r2_fit = linear_model.score(log_nunits_fit[:, np.newaxis], log_errors_fit)

power_law_exponent_parameter = - linear_model.coef_[0]
power_law_scaling_parameter = np.exp(linear_model.intercept_)

log_error_extrapolation_predicted = linear_model.predict(np.array([[log_nunits_extrapolation]]))[0]
performance_extrapolation_predicted = 100 - np.exp(log_error_extrapolation_predicted)

# R2 on fitted data points
output_string = f"R2 of the linear regression on the fitted data points: {r2_fit}"
# Estimated parameters of the power law
output_string += f"\nEstimated exponent parameter of the power law: {power_law_exponent_parameter}"
output_string += f"\nEstimated scaling constant parameter of the power law: {power_law_scaling_parameter}"
# Predicted performance at the extrapolation data point
output_string += f"\nPerformance predicted by the power law at N={n_units_extrapolation} units: {performance_extrapolation_predicted}"
# Actual performance at the extrapolation data point
output_string += f"\nActual measured performance at N={n_units_extrapolation} units: {performance_extrapolation_measured}"

if out_path is not None:
    with open(out_path, 'w') as f_out:
        print(output_string, file=f_out)
    print("Results saved at", out_path)
else:
    print(output_string)


"""Check if states form mutually unbiased basis."""
from typing import Any, List, Union

import numpy as np

def is_mutually_unbiased_basis(vec_list: List[Union[np.ndarray, List[Union[float, Any]]]]) -> bool:
    r"""
    Check if list of vectors constitute a mutually unbiased basis [WikMUB]_.

    We say that two orthonormal bases

    .. math::
        \begin{equation}
            \mathcal{B}_0 = \left\{u_a: a \in \Sigma \right\} \subset \mathbb{C}^{\Sigma}
            \quad \text{and} \quad
            \mathcal{B}_1 = \left\{v_a: a \in \Sigma \right\} \subset \mathbb{C}^{\Sigma}
        \end{equation}

    are *mutually unbiased* if and only if
    :math:`\left|\langle u_a, v_b \rangle\right| = 1/\sqrt{\Sigma}` for all :math:`a, b \in \Sigma`.

    For :math:`n \in \mathbb{N}`, a set of orthonormal bases :math:`\left\{
    \mathcal{B}_0, \ldots, \mathcal{B}_{n-1} \right\}` are mutually unbiased bases if and only if
    every basis is mutually unbiased with every other basis in the set, i.e. :math:`\mathcal{B}_x`
    is mutually unbiased with :math:`\mathcal{B}_x^{\prime}` for all :math:`x \not= x^{\prime}` with
    :math:`x, x^{\prime} \in \Sigma`.

    Examples
    ==========

    MUB of dimension :math:`2`.

    For :math:`d=2`, the following constitutes a mutually unbiased basis:

    .. math::
        \begin{equation}
            \begin{aligned}
                M_0 &= \left\{ |0 \rangle, |1 \rangle \right\}, \\
                M_1 &= \left\{ \frac{|0 \rangle + |1 \rangle}{\sqrt{2}},
                \frac{|0 \rangle - |1 \rangle}{\sqrt{2}} \right\}, \\
                M_2 &= \left\{ \frac{|0 \rangle i|1 \rangle}{\sqrt{2}},
                \frac{|0 \rangle - i|1 \rangle}{\sqrt{2}} \right\}. \\
            \end{aligned}
        \end{equation}

    >>> import numpy as np
    >>> from toqito.states import basis
    >>> from toqito.state_props import is_mutually_unbiased_basis
    >>> e_0, e_1 = basis(2, 0), basis(2, 1)
    >>> mub_1 = [e_0, e_1]
    >>> mub_2 = [1 / np.sqrt(2) * (e_0 + e_1), 1 / np.sqrt(2) * (e_0 - e_1)]
    >>> mub_3 = [1 / np.sqrt(2) * (e_0 + 1j * e_1), 1 / np.sqrt(2) * (e_0 - 1j * e_1)]
    >>> mubs = [mub_1, mub_2, mub_3]
    >>> is_mutually_unbiased_basis(mubs)
    True

    Non non-MUB of dimension :math:`2`.

    >>> import numpy as np
    >>> from toqito.states import basis
    >>> from toqito.state_props import is_mutually_unbiased_basis
    >>> e_0, e_1 = basis(2, 0), basis(2, 1)
    >>> mub_1 = [e_0, e_1]
    >>> mub_2 = [1 / np.sqrt(2) * (e_0 + e_1), e_1]
    >>> mub_3 = [1 / np.sqrt(2) * (e_0 + 1j * e_1), e_0]
    >>> mubs = [mub_1, mub_2, mub_3]
    >>> is_mutually_unbiased_basis(mubs)
    False

    References
    ==========
    .. [WikMUB] Wikipedia: Mutually unbiased bases
        https://en.wikipedia.org/wiki/Mutually_unbiased_bases

    :param vec_list: The list of vectors to check.
    :return: :code:`True` if :code:`vec_list` constitutes a mutually unbiased basis, and
             :code:`False` otherwise.
    """
    if len(vec_list) <= 1:
        raise ValueError("There must be at least two bases provided as input.")

    dim = vec_list[0][0].shape[0]
    for i, _ in enumerate(vec_list):
        for j, _ in enumerate(vec_list):
            for k in range(dim):
                if i != j:
                    if not np.isclose(
                        np.abs(np.inner(vec_list[i][k].conj().T[0], vec_list[j][k].conj().T[0]))
                        ** 2,
                        1 / dim,
                    ):
                        return False
    return True


import numpy as np

def normalize(data, norm_params, method='zscore'):
    """
    Normalize time series
    :param data: time series
    :param norm_params: tuple with params mean, std, max, min
    :param method: zscore or minmax
    :return: normalized time series
    """
    assert method in ['zscore', 'minmax', None]

    if method == 'zscore':
        return (data - norm_params['mean']) / norm_params['std']

    elif method == 'minmax':
        return (data - norm_params['min']) / (norm_params['max'] - norm_params['max'])

    elif method is None:
        return data

def denormalize(data, norm_params, method='zscore'):
    """
    Reverse normalization time series
    :param data: normalized time series
    :param norm_params: tuple with params mean, std, max, min
    :param method: zscore or minmax
    :return: time series in original scale
    """
    assert method in ['zscore', 'minmax', None]

    if method == 'zscore':
        return (data * norm_params['std']) + norm_params['mean']

    elif method == 'minmax':
        return (data * (norm_params['max']) - (norm_params['min']) + norm_params['max'])

    elif method is None:
        return data

def denormalize_dataset(dataset, norm_params_list, method='zscore'):
    """
    Reverse normalization on complete time series dataset
    :param dataset: array of all time series
    :param norm_params_list: array with tuples of normalization params for each time series
    :param method: zscore or minmax
    :return: array with all time series at original scale
    """
    return np.asarray([denormalize(dataset[i], norm_params_list[i], method) for i in range(dataset.shape[0])])

def get_normalization_params(data):
    """
    Obtain parameters for normalization
    :param data: time series
    :return: dict with string keys
    """
    norm_params = {}
    norm_params['mean'] = data.mean()
    norm_params['std'] = data.std()
    norm_params['max'] = data.max()
    norm_params['min'] = data.min()

    return norm_params


# Author: Sylvain Lapeyrade (sylla801)
# File: TSKS11-Lab2: Task 3

# ***************************** TASK 3 *****************************
# Calculate the Katz Centrality of each article with
#  lamdba = 0.85 * (1/|lamdba_max|), where lamdba_max is the largest
#  eigenvalue of the adjacency matrix.
# List the top five articles and their Katz score.

import numpy as np

# The calculations being too expensive, only one file
#  will be dealt with at the same time

network_number = 1  # Number of the file wanted

title_file = open(
    '/courses/TSKS11/ht2019/data_and_fcns/session2/titles/{}.txt'
    .format(network_number), 'r')
link_file = open(
    '/courses/TSKS11/ht2019/data_and_fcns/session2/links/{}.txt'
    .format(network_number), 'r')

title_list = []
titles = title_file.read().splitlines()  # Array of the titles
for title in titles:
    title_list.append(title)

degree = np.loadtxt(link_file, dtype=int)  # Loads all links
n = max(map(max, degree))  # Number of nodes

m = np.zeros((n, n), dtype=float)  # Matrix M initialized with 0
for j in range(len(degree)):  # Put 1 for every links
    m[degree[j][1] - 1][degree[j][0] - 1] = 1

identity_matrix = np.identity(n)  # Identity Matrix 3000x3000

# Compute the eigenvalues of m.
katz_eigenvalues = np.linalg.eigvals(m)

# Vector with only ones (3000, 1)
vector_I = np.ones((n, 1), dtype=int)

alpha = 0.85 * (1 / max(abs(katz_eigenvalues)))  # Alpha as instrcuted

# Katz centrality formula
katz_centrality = (1 / n) * np.matmul(np.linalg.inv(
    identity_matrix - (alpha * m)), vector_I)

# Sum of all katz centrality
total_katz_centrality = float(np.sum(katz_centrality, axis=0))

for i in range(n):
    katz_centrality[i] /= total_katz_centrality  # Normalisation

# Arguments of the fives largest katz centrality
five_argmax_katz = katz_centrality.argsort(axis=None)[-5:][::-1]

print("\n\nNetwork {}: \n".format(network_number))
print("Nodes with the highest Katz Centrality:")
for i in range(5):
    print(str(i + 1) + '.', "Katz-Centrality: {0:.6f}"
          .format(round(float(katz_centrality[five_argmax_katz[i]]), 6)),
          "| Title:", title_list[five_argmax_katz[i]])


#!/usr/bin/python3
"""
Program: 95% Confidence Interval of a Proportion Simulation
Author: Nathan Fox <nchristopherfox@gmail.com>
Date Created: 2019-05-28
Date Modified: 2019-05-28

This program conducts a simulation to confirm the modified Wald's method
for calculating a 95% Confidence Interval (CI) for a proportion. It takes a
population proportion parameter, then draws s trials for n simulations
and reports the percentage of simulations where the calculated CI contained
the population proportion parameter.

Modified Wald's Method for 95% CI for a Proportion was taken from pg 39 of
Intuitive Biostatistics by Harvey Motulsky, ISBN-13 978-0-19-064356-0
"""

import argparse
import numpy as np

def mod_wald(successes, trials):
    """Calculate Modified Wald's Method 95% Confidence Interval for Proportion.

    Calculates a 95% confidence interval sourced from Intuitive Biostatistics,
    see note in script docstring. This method assumes several things:

    1) The sample was taken from the same distribution as the population.
       i.e. This sample is representative.
    2) All observations are independently drawn.
    3) All observations are reported accurately.

    Args:
        successes: integer; number of trials that gave the desired outcome
        trials: integer; total number of trials

    Returns:
        A tuple with 2 elements. tuple[0] is the lower bound of the confidence
        interval. tuple[1] is the upper bound.
    """
    p_hat = (successes + 2) / (trials + 4)
    w = 2 * np.sqrt((p_hat * (1-p_hat)) / (trials + 4))
    return (p_hat-w, p_hat+w)

def simulate(x, n, sample_size):
    """Simulate calculation of 95% CI for proportions from binomial data.

    Simulates n experiments, drawing sample_size data points from a distribution
    with a favorable proportion of x. Calculates a confidence interval for
    each, then displays the number of confidence intervals that actually
    contained the population parameter.

    Args:
        x: float; population proportion for favorable outcome
        n: integer; number of simulations
        sample_size: integer; number of trials per simulation

    Returns:
        Numpy array of size(n, 3). Each row is a simulation.
        Column 0: Lower bound of CI
        Column 1: Upper bound of CI
        Column 3: Float form of boolean(CI contains population parameter)
    """
    if x > 1.0 or x < 0.0:
        raise ValueError('x must be in the closed interval [0, 1]!')
    results = np.zeros((n, 3))
    for i in range(n):
        s = np.random.uniform(size=sample_size)
        s = s < x
        results[i] = (mod_wald(successes=s.sum(), trials=sample_size) + (-1.0,))
        results[i, 2] = float((results[i, 0] <= x) and (results[i, 1] >= x))
    return results

def main():
    """Simulation to verify 95% Confidence Interval for Proportion.

    Prints n, number of simulations where the CI actually contained the
    population parameter, and the percentage version of the last value. To see
    command line arguments, run 'python conf_int_proportion_sim.py -h'.
    """
    parser = argparse.ArgumentParser(description=('Confirms 95% Confidence '
                                        'Interval by simulation. Sample size '
                                        'of 40 per simulation. Takes 1 '
                                        'parameter:\n\tP: Population '
                                        'percentage of favorable outcomes'))
    parser.add_argument('P', type=float,
                        help='Proportion of population with favorable outcome')
    parser.add_argument('-n', default=100, type=int,
                        help='Number of simulations to be run')
    parser.add_argument('-s', default=40, type=int,
                        help=('Number of trials in each simulation '
                              '(sample size)'))

    args=parser.parse_args()
    results = simulate(x=args.P, n=args.n, sample_size=args.s)
    print('\nResults\n====================\n')
    # print(results)
    print('# of Simulations:           {}'.format(args.n))
    print('# of CIs Including True P:  {}'.format(int(results[:,2].sum())))
    print('% of Sims Including True P: {}'.format(results[:,2].sum()/args.n))
    print()

if __name__=='__main__':
    main()


import numpy as np
from scipy.linalg import svd
from sklearn.decomposition import TruncatedSVD

if __name__ == '__main__':
    A = np.array([[1, 2, 3],
                  [3, 2, 5],
                  [5, 6, 1]])
    U, s, VT = svd(A)
    print(U)
    print(s)
    print(VT)
    print()

    Sigma = np.zeros(A.shape)
    for i in range(len(s)):
        Sigma[i][i] = s[i]
    print(Sigma)
    print(U.dot(Sigma).dot(VT))

    print(U[:, :2].dot(Sigma[:2, :2]).dot(VT[:2, :]))


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

'''
A 2-layer network, to model the XOR and XNOR operator.

This model is actually not good, so you should just do this as an exercise.
'''

# create the input vector X
X = np.zeros( (4,2) )
X[0,0] = 0; X[0,1] = 0
X[1,0] = 0; X[1,1] = 1
X[2,0] = 1; X[2,1] = 0
X[3,0] = 1; X[3,1] = 1
X = np.concatenate(  (np.ones((X.shape[0],1) ),X) ,axis=1)
print('X is \n',X,'\n')

# create the true solution vector Z
Z = np.zeros( (4,1) )
Z[0,0] = 0
Z[1,0] = 1
Z[2,0] = 1
Z[3,0] = 0
print('Z is \n',Z,'\n')

# chose random values for the weights
scale = 1

U = np.random.rand(3,2)*2*scale - scale
v = np.random.rand(3,1)*2*scale - scale

print('U initial is \n',U,'\n')
print('v initial is \n',v,'\n')

# gradient descent step
alpha = .2;

nIt = 100000;

# create an error matrix to plot the gradient descent results
E = np.zeros((nIt,1))

for i in range(0,nIt):

    Y = np.dot(X,U)
    A0 = sp.special.expit(Y)
    A = np.concatenate(  (np.ones((A0.shape[0],1) ),A0) ,axis=1)
    #print('Y is \n',Y,'\n')

    h = sp.special.expit(np.dot(A,v))

    #print('Zp is \n',Zp,'\n')
    d = Z - h

    D = np.diag(d[:,0])

    U = U + alpha*np.dot(np.dot(X.T,np.dot(D,np.multiply(A0,1-A0))), np.diag(v[1:,0]))

    v = v + alpha*np.dot(A.T,d)

    E[i] = np.dot(d.T,d)

print('U final initial is \n',U,'\n')
print('v final initial is \n',v,'\n')

xplot = list(range(1,nIt+1))
plt.semilogx(np.log(xplot),E, "-o", color="r")
plt.tight_layout()
plt.xlabel('# iterations')
plt.ylabel('error')
print('the final result with a .5 threshold is \n',np.round(h))


from math import log2 as lg, pow, exp
import numpy as np

def entropy(arr):
    ent = 0

    for p in arr:
        if p <= 0: continue
        ent -= p * lg(p)

    return ent

def get_py_array(px, yx_matrix):
    py_array = [round(sum([px[i] * yx_matrix[i][j] for i in range(len(yx_matrix))]), 4) for j in range(len(yx_matrix))]
    return py_array

def get_entropy_x(px):
    hx = entropy(px)
    return hx

def get_entropy_y(px, yx_matrix):
    py_array = get_py_array(px, yx_matrix)
    hy = entropy(py_array)
    return hy

def get_entropy_yx(px, yx_matrix):
    hyx = sum([sum(px[i] * entropy(yx_matrix[i]) for i in range(len(yx_matrix)))])

    return hyx

def get_entropy_xy(px, yx_matrix):
    # py_array = get_py_array(px, yx_matrix)
    return get_entropy_x(px) - get_entropy_y(px, yx_matrix) + get_entropy_yx(px, yx_matrix)
    # new_matrix = [[yx_matrix[i][j] for i in range(len(yx_matrix))] for j in range(len(yx_matrix))]
    # hxy = sum([sum(py_array[i] * entropy(new_matrix[i]) for i in range(len(new_matrix)))])
    #
    # return hxy

def print_all(p, m):
    print("p(y) array:", get_py_array(p, m))
    print("H(X) =", get_entropy_x(p))
    print("H(Y) =", get_entropy_y(p, m))
    print("H(Y|X) =", get_entropy_yx(p, m))
    print("H(X|Y) = ", get_entropy_xy(p, m))
    print(lg(3))

if __name__ == "__main__":
    px = [0.4, 0.2, 0.4]
    # yx_matrix = [[0.15, 0.25, 0.6],
    #              [0.35, 0.25, 0.4],
    #              [0.49, 0.5, 0.01]]

    yx_matrix = [[0.7, 0.2, 0.1],
                 [0.1, 0.7, 0.2],
                 [0.2, 0.1, 0.7]]

    pxx = [10/43, 9/43, 5/43, 6/43, 7/43, 4/43, 1/43, 1/43]
    print(entropy(pxx))

    print_all(px, yx_matrix)


from sympy import ( symbols, solve, diff, integrate, exp, sqrt, lambdify, Integral, pprint )
import matplotlib.pyplot as plt
import numpy as np

# The probability density function of a random variable on the interval [16, 25]

x = symbols( 'x' )
F = ( 3/122 ) * sqrt( x )

a, b = 16, 25

def expected_value( f, var, a, b ):
	return integrate( var * f, ( var, a, b ) )

def variance( f, var, a, b, mu ):
	return integrate( ( ( var **2 ) * f ), ( var, a, b ) ) - mu**2

def std_dev( var ):
	return sqrt( var )

# Find the expected value.
ev = expected_value( F, x, a, b )
round( ev, 2 )

# Find the variance.
variance = variance( F, x, a, b, ev )
round( variance, 2 )

# Find the standard deviation.
stdev = std_dev( variance )
round( stdev, 2 )

# Find the probability that the random variable has a value greater than the mean.
prob_gt_mean = integrate( F, ( x, ev, b ) )
round( prob_gt_mean, 2 )

# find the probability that the value of the random variable is within one standard deviation of the mean.
prob_within_1sd = integrate( F, ( x, ev - stdev, ev + stdev ) )
round( prob_within_1sd, 2 )


'''
Perform PCA analysis
'''
import numpy as np

def pca(samples, mean=None, max_variance=1, full_matrices=False):
    '''
    Perform PCA analysis on samples according to an amount of principal
    components.

    in: 1xC vector mean of all samples
        RxC matrix containing the samples
        int maximal variance --> limits the amount of returned
        eigenvectors/values
    out: Rx? matrix of eigenvectors/eigenvalues
        eigenvalues and eigenvectors of the covariance matrix such that their
        total variance is not lower than max_variance.
        1xC vector the average sample
    '''
    if not bool(np.sum(mean)):
        mean = np.mean(samples, axis=0)

    # create difference between samples and mean
    deviation_from_mean = samples - mean

    # perform singular value decomposition to get eigenvectors and eigenvalues
    eigenvectors, eigenvalues, variance = np.linalg.svd(
                                            deviation_from_mean.T,
                                            full_matrices=False
                                            )

    if not full_matrices:
        # only keep eigenvalues that add up to max_variance * total variance
        total_variance = np.sum(eigenvalues)
        for i in range(eigenvalues.size):
            if np.sum(eigenvalues[:i]) >= max_variance*total_variance:
                eigenvalues = eigenvalues[:i]
                eigenvectors = eigenvectors[:, :i]

    return eigenvalues, eigenvectors, mean


"""
Tests for the best response check
"""
import numpy as np

from nashpy.utils.is_best_response import (
    is_best_response,
)

def test_is_best_response_example_1():
    """
    This tests an example from the discussion documentation.

    The second assert checks that the column player strategy is as expected.
    """
    A = np.array(((0, -1, 1), (1, 0, -1), (-1, 1, 0)))
    sigma_c = np.array((0, 1 / 2, 1 / 2))
    sigma_r = np.array((0, 0, 1))
    assert is_best_response(A=A, sigma_c=sigma_c, sigma_r=sigma_r) is True
    assert is_best_response(A=-A.T, sigma_c=sigma_r, sigma_r=sigma_c) is False

def test_is_best_response_example_2():
    """
    This tests an example from the discussion documentation.

    The second assert checks that the column player strategy is as expected.
    """
    A = np.array(((0, -1, 1), (1, 0, -1), (-1, 1, 0)))
    sigma_c = np.array((0, 1 / 2, 1 / 2))
    sigma_r = np.array((1 / 3, 1 / 3, 1 / 3))
    assert is_best_response(A=A, sigma_c=sigma_c, sigma_r=sigma_r) is False
    assert is_best_response(A=-A.T, sigma_c=sigma_r, sigma_r=sigma_c) is True

def test_is_best_response_example_3():
    """
    This tests an example from the discussion documentation.

    The second assert checks that the column player strategy is as expected.
    """
    A = np.array(((0, -1, 1), (1, 0, -1), (-1, 1, 0)))
    sigma_c = np.array((1 / 3, 1 / 3, 1 / 3))
    sigma_r = np.array((1 / 3, 1 / 3, 1 / 3))
    assert is_best_response(A=A, sigma_c=sigma_c, sigma_r=sigma_r) is True
    assert is_best_response(A=-A.T, sigma_c=sigma_r, sigma_r=sigma_c) is True


# -*- coding: utf-8 -*-
"""
Created on Sat Oct 02 17:12:25 2018

Polynomial Regression Model to help HR department of an organization find
if the previous salary quoted by the potential future employee is correct (truth or bluff)
based on the designation-wise salary data of the past employer

@author: Priyanka Sharma
"""

# Importing the libraries
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd

# Importing the dataset
dataset = pd.read_csv('Position_Salaries.csv')
# Independant Variable
X = dataset.iloc[:, 1:2].values
# Dependant Variable
y = dataset.iloc[:, 2].values

# Splitting the dataset into the Training set and Test set
# Not required since we do not have much data so we cannot split
# Also, we need to use all data to make accurate prediction
"""from sklearn.cross_validation import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = 0.2, random_state = 0)"""

# Feature Scaling
# Not required as it is taken care of by the library
"""from sklearn.preprocessing import StandardScaler
sc_X = StandardScaler()
X_train = sc_X.fit_transform(X_train)
X_test = sc_X.transform(X_test)
sc_y = StandardScaler()
y_train = sc_y.fit_transform(y_train)"""

# Fitting Linear Regression to the dataset
# added for comparison with Polynomial Regression Model
from sklearn.linear_model import LinearRegression
lin_reg = LinearRegression()
lin_reg.fit(X,y)

# Fitting Polynomial Regression to the dataset
from sklearn.preprocessing import PolynomialFeatures
# below code transforms X into new matrix containing independent variables with different powers
# degree can be adjusted to see which model fits better
poly_reg = PolynomialFeatures(degree =4)
X_poly = poly_reg.fit_transform(X)
lin_reg_2 = LinearRegression()
lin_reg_2.fit(X_poly, y)

# Visualizing Linear Regression results
# added for comparison with Polynomial Regression Model
plt.scatter(X, y, color = 'red')
plt.plot(X, lin_reg.predict(X), color = 'blue')
plt.title('Truth or Bluff (Linear Regression)')
plt.xlabel('Position Level')
plt.ylabel('Salary')
plt.show()

# Visualizing Polynomial Regression results
plt.scatter(X, y, color = 'red')
plt.plot(X, lin_reg_2.predict(poly_reg.fit_transform(X)), color = 'blue')
plt.title('Truth or Bluff (Polynomial Regression)')
plt.xlabel('Position Level')
plt.ylabel('Salary')
plt.show()

# Visualizing Polynomial Regression results
# X_grid contains all the levels along with incremented levels with resolution of 0.1
X_grid = np.arange(min(X), max(X), 0.1)
X_grid = X_grid.reshape((len(X_grid), 1))
plt.scatter(X, y, color = 'red')
plt.plot(X_grid, lin_reg_2.predict(poly_reg.fit_transform(X_grid)), color = 'blue')
plt.title('Truth or Bluff (Polynomial Regression)')
plt.xlabel('Position Level')
plt.ylabel('Salary')
plt.show()

# Predicting a new result with Linear Regression
# added for comparison with Polynomial Regression Model
lin_reg.predict(6.5)

# Predicting a new result with Polynomial Regression
lin_reg_2.predict(poly_reg.fit_transform(6.5))