#%% [markdown]
# # K-means Algorithm

#%%
# Libraries Imports
import numpy as np
import pandas as pd

#%%
# Initialize Data Points
""" (x-axis, y-axis) """
data_points = [(5.9, 3.2), (4.6, 2.9), (6.2, 2.8), (4.7, 3.2), (5.5, 4.2), (5.0, 3.0), (4.9, 3.1), (6.7, 3.1), (5.1, 3.8), (6.0, 3.0)]
len(data_points)

#%%
# Euclidian distance
def euclidian_dist(p1, p2):
  p1_arr = np.array(p1)
  p2_arr = np.array(p2)
  return np.linalg.norm(p1_arr-p2_arr)

""" Tests: euclidian_dist(p1, p2) """
p1 = (5.9, 3.2)
p2 = (5.9, 3.2)
print('Test 1 - p1 = p1: ' + str(euclidian_dist(p1, p2)))

p1 = (1.0, 0.0)
p2 = (0.0, 1.0)
print('Test 2: root(2): ' + str(euclidian_dist(p1, p2)))

p1 = (2.0, 0.0)
p2 = (1.0, 3.0)
print('Test 3: root(10): ' + str(euclidian_dist(p1, p2)))

p1 = (6.2, 3.2)
p2 = (5.9, 3.2)
print('Test 4: dist(c1, dp1): ' + str(euclidian_dist(p1, p2)))

p1 = (6.6, 3.7)
p2 = (5.9, 3.2)
print('Test 5: dist(c2, dp1): ' + str(euclidian_dist(p1, p2)))

p1 = (6.5, 3.0)
p2 = (5.9, 3.2)
print('Test 6: dist(c3, dp1): ' + str(euclidian_dist(p1, p2)))

#%%
# Initialize centroids: K = 3
c1 = (6.2, 3.2) # red
c2 = (6.6, 3.7) # green
c3 = (6.5, 3.0) # blue

centroids = [c1, c2, c3]
centroids

#%%
# Calculate the distance of all data points
def calculate_dist_all(data_points, centroids):
  distances = []
  for point in data_points:
    distance_per_point = []
    for centroid in centroids:
      dist = euclidian_dist(centroid, point)
      distance_per_point.append(dist)
    distances.append(distance_per_point)
  return distances

distances = calculate_dist_all(data_points, centroids)

df = pd.DataFrame(data=distances)
df.head()

#%%
# Calculate the nearest centroid to each data point
groups = df.idxmin(axis=1)
groups.head()

#%%
# Assign data points to each centroid
clusters = [[], [], []]
count = 0
for dp in groups.values:
  clusters[dp].append(data_points[count])
  count += 1

clusters

#%%
# Recalculate centroids
c_centroids = 0
for cluster in clusters:
  x_axis = 0
  y_axis = 0
  for dp in cluster:
    x_axis += dp[0] / len(cluster)
    y_axis += dp[1] / len(cluster)
  centroids[c_centroids] = (x_axis, y_axis)
  c_centroids += 1

centroids


import numpy as np
import scipy.stats as sct
import time
import itertools
import sys

#----- normal distribution

def normal_model_log_prob(_x, _theta):
    #-- parameters
    _mu = _theta[0]
    _sigma_sq = _theta[1]
    #-- log probability
    _p = -(_x - _mu) ** 2 / (2 * _sigma_sq) - np.log(2 * np.pi * _sigma_sq) / 2
    return _p

def normal_max_likelihood_est(_x):
    _mu = np.mean(_x)
    _sigma_sq = np.var(_x)
    _theta = [_mu, _sigma_sq]
    return _theta

def normal_log_likelihood(_x, _theta):
    _l = np.sum(normal_model_log_prob(_x, _theta))
    return _l

#-----

def model_log_prob(_x, _theta):
    _beta = _theta[0]
    _sigma_sq = _theta[1]
    _X = _x[:, 0:-1]
    _Y = _x[:, -1]
    _z = _Y - _X @ _beta
    _prob = normal_model_log_prob(_z, [0, _sigma_sq])
    return _prob

def max_likelihood_est(_x, _k):
    _X = _x[:, 0:_k]
    _Y = _x[:, -1]
    _beta = np.linalg.solve(_X.T @ _X, _X.T @ _Y)
    _beta.resize(_x.shape[1] - 1)
    _sigma_sq = np.mean((_Y - _x[:, 0:-1] @ _beta) ** 2)
    _theta = [_beta, _sigma_sq]
    return _theta

def bootstrap_residual(_x, _ids, _theta):
    return _x

#-----

def log_likelihood(_x, _theta):
    _l = np.sum(model_log_prob(_x, _theta))
    return _l

def bootstrap_sample(_x, _theta):
    _n = _x.shape[0]
    _ids = np.random.randint(0, _n, _n)
    _x_ast = _x[_ids, :]

    _x_ast_r = bootstrap_residual(_x_ast, _ids, _theta)

    return _x_ast_r

def EIC_biasE(_x, _k, _B):
    _D_ast = np.zeros((_B, 2))
    _theta = max_likelihood_est(_x, _k)
    for i in range(_B):
        _x_ast = bootstrap_sample(_x, _theta)
        _theta_ast = max_likelihood_est(_x_ast, _k)
        _D_ast[i, 0] = log_likelihood(_x_ast, _theta_ast) - log_likelihood(_x_ast, _theta)
        _D_ast[i, 1] = log_likelihood(_x, _theta) - log_likelihood(_x, _theta_ast)

    _b_b = np.mean(_D_ast[:, 0] + _D_ast[:, 1])
    return _b_b

#-----

def main(T, n, K, k, B):
    t_l = np.zeros(T)
    t_bias = np.zeros(T)

    prv_ut = time.time()
    for t in range(T):
        ut = time.time()
        if ut - prv_ut > 5.0:
            prv_ut = ut
            print("---", t, file=sys.stderr)

        #-- samples from true distribution
        #X = np.random.uniform(-1.0, 1.0, (n, K))
        X = np.random.normal(0.0, 1.0, (n, K))
        Y = np.random.normal(0.0, 1.0, (n, 1))
        x = np.hstack((X, Y))

        theta = max_likelihood_est(x, k)
        t_l[t] = log_likelihood(x, theta)

        t_bias[t] = EIC_biasE(x, k, B)

    print(k, # k
          np.mean(t_bias), # bias, mean
          np.mean(t_l), # likelihood, mean
          )

if __name__ == '__main__':

    T = 10 #1000

    n = 100
    K = 20

    B = 100

    for k in range(21):
        main(T, n, K, k, B)


#!/usr/bin/env python                                                                                #
# ------------------------------------------------------------------------------------------------------#
# Created by "Thieu Nguyen" at 02:52, 07/12/2019                                                        #
#                                                                                #
#       Email:      nguyenthieu2102@gmail.com                                                           #
#       Homepage:   https://www.researchgate.net/profile/Thieu_Nguyen6                                  #
#       Github:     https://github.com/thieunguyen5991                                                  #
#-------------------------------------------------------------------------------------------------------#

import numpy as np

class Functions:
    """
        This class of functions is belongs to 1-dimensional space
    """

    def _gramacy_lee__(self, solution=None):
        """
        Class: uni-modal, non-convex, continuous
        Global: 1 global minimum fx = −0.869011134989500, atx 0.548563444114526
        Link: http://benchmarkfcns.xyz/benchmarkfcns/gramacyleefcn.html

        @param solution: A numpy array include 1 items like: [0.5], limited range: [-0.5, 2.5]

        """
        n = len(solution)
        assert (n == 1, 'Gramacy and Lee function is only defined on a 1D space.')
        return np.sin(10*np.pi*solution) / (2*solution) + (solution-1)**4


import math
import numpy as np

class AdaBoostHelper:
    def __init__(self, X, y, n, m):
        self.cache = dict()
        self.X = X.toarray()

        self.y = np.ones(n)
        self.y[y == 0] = -1
        self.n = n
        self.m = m
        self.D = np.array([1 / n for i in range(n)])

    def calc_yhat(self, j, c):
        y_hat_arr = np.ones(self.n)
        dimension = self.X[:, j]
        y_hat_arr[dimension <= c] = -1

        if np.sum(y_hat_arr == self.y) < self.n / 2:
            return -y_hat_arr, -1
        return y_hat_arr, 1

    def calc_ht(self):
        j_t = None
        c_t = None
        y_hat_arr_t = None
        y_hat_val_t = None
        error = 1

        for j in range(self.m):
            j_arr_sorted = None
            if j in self.cache:
                j_arr_sorted = self.cache[j]
            else:
                # j_arr_sorted = np.sort(np.unique(self.X[:, j]))
                j_arr_sorted = np.sort(self.X[:, j])
                self.cache[j] = j_arr_sorted

            c_list = np.unique((j_arr_sorted[:self.n - 1] + j_arr_sorted[1:]) / 2)
            # print(c_list)
            # jl = len(j_arr_sorted)
            # print(jl)

            for c in c_list:
                # c = (j_arr_sorted[k] + j_arr_sorted[k + 1]) / 2
                y_hat_arr, y_hat_val = self.calc_yhat(j, c)
                curr_error_arr = np.zeros(self.n)
                curr_error_arr[y_hat_arr != self.y] = 1
                curr_error = np.dot(self.D, curr_error_arr)
                # print('j: {}, k: {}, c: {}, e: {}'.format(j, k, c, curr_error))

                if curr_error < error:
                    j_t = j
                    c_t = c
                    y_hat_arr_t = y_hat_arr
                    y_hat_val_t = y_hat_val
                    error = curr_error

        return j_t, c_t, y_hat_arr_t, y_hat_val_t, error

    def calc_alphat(self, error):
        return 0.5 * math.log((1 / error) - 1)

    def set_next_distribution(self, alpha, y_hat):
        self.D = np.multiply(self.D, np.exp(np.multiply(-alpha * self.y, y_hat)))
        Z = self.D.sum()
        self.D /= Z

    @staticmethod
    def ht(X, h_arg):
        num_of_features = X.shape[0]
        j, c, y_hat_arr, y_hat_val, error = h_arg
        res = np.zeros(num_of_features)
        dimension = X[:, j]
        res[dimension > c] = y_hat_val
        res[dimension <= c] = -y_hat_val
        return res


import sympy
import matplotlib.pyplot as pyplot

class ODE:

    moulton_coeff = [[1, 0, 0, 0, 0, 0, 0, 0],
                     [1 / 2, 1 / 2, 0, 0, 0, 0, 0, 0],
                     [5 / 12, 2 / 3, -1 / 12, 0, 0, 0, 0, 0],
                     [3 / 8, 19 / 24, -5 / 24, 1 / 24, 0, 0, 0, 0],
                     [251 / 720, 323 / 360, -11 / 30, 53 / 360, -19 / 720, 0, 0, 0],
                     [95 / 288, 1427 / 1440, -133 / 240, 241 / 720, -173 / 1440, 3 / 160, 0, 0],
                     [19087 / 60480, 2713 / 2520, -15487 / 20160, 586 / 945, -6737 / 20160, 263 / 2520, -863 / 60480, 0],
                     [5257 / 17280, 139849 / 120960, -4511 / 4480, 123133 / 120960, -88547 / 120960, 1537 / 4480, -11351 / 120960, 275 / 24192]]

    bashforth_coeff = [[1, 0, 0, 0, 0, 0, 0, 0],
                       [3 / 2, -1 / 2, 0, 0, 0, 0, 0, 0],
                       [23 / 12, -4 / 3, 5 / 12, 0, 0, 0, 0, 0],
                       [55 / 24, -59 / 24, 37 / 24, -3 / 8, 0, 0, 0, 0],
                       [1901 / 720, -1387 / 360, 109 / 30, -637 / 360, 251 / 720, 0, 0, 0],
                       [4277 / 1440, -2641 / 480, 4991 / 720, -3649 / 720, 959 / 480, -95 / 288, 0, 0],
                       [198721 / 60480, -18637 / 2520, 235183 / 20160, -10754 / 945, 135713 / 20160, -5603 / 2520, 19087 / 60480, 0],
                       [16083 / 4480, -1152169 / 120960, 242653 / 13440, -296053 / 13440, 2102243 / 120960, -115747 / 13440, 32863 / 13440, -5257 / 17280]]

    inverse_coeff = [[1, 1, 0, 0, 0, 0, 0],
                     [2.0 / 3.0, 4.0 / 3.0, -1.0 / 3.0, 0, 0, 0, 0],
                     [6.0 / 11.0, 18.0 / 11.0, -9.0 / 11.0, 2.0 / 11.0, 0, 0, 0],
                     [12.0 / 25.0, 48.0 / 25.0, -36.0 / 25.0, 16.0 / 25.0, -3.0 / 25.0, 0, 0],
                     [60.0 / 137.0, 300.0 / 137.0, -300.0 / 137.0, 200.0 / 137, -75.0 / 137.0, 12.0 / 137.0, 0],
                     [60.0 / 147.0, 360.0 / 147.0, -450.0 / 147.0, 400.0 / 147.0, -255.0 / 147.0, 72.0 / 147.0, 10.0 / 147.0]]

    def __init__(self, y, t, h, func, order=1):
        self.y = y
        self.t = t
        self.h = h
        self.func = func
        self.order = order

    def euler(self):
        return self.y + self.h * self.func(self.t, self.y)

    def backward_euler(self):
        y1 = self.euler()
        return self.y + self.h * self.func(self.t + self.h, y1)

    def modified_euler(self):
        y1 = self.euler()
        return self.y + (self.h / 2) * (self.func(self.t, self.y) + self.func(self.t + self.h, y1))

    def runge_kutta(self, params=None):
        if params:
            t = params[0]
            y = params[1]
        else:
            t = self.t
            y = self.y

        k1 = self.func(t, y)
        k2 = self.func(t + self.h / 2, y + k1 * self.h / 2)
        k3 = self.func(t + self.h / 2, y + k2 * self.h / 2)
        k4 = self.func(t + self.h, y + k3 * self.h)
        return y + (k1 + 2 * k2 + 2 * k3 + k4) * self.h / 6

    def adam_bashforth(self, i):
        fs = []
        # y = self.h * sum([self.bashforth_coeff[self.order-1][j] * self.func(t_aux[j], self.y[i]) for j in range(self.order)])
        for j in range(self.order):
            t = self.t[i - j]
            y = self.y[i - j]
            fs.append(self.func(t, y))

        y = self.h * sum([(self.bashforth_coeff[self.order - 1][j] * fs[j]) for j in range(self.order)])
        # for j, f in enumerate(fs):
        #    y += self.bashforth_coeff[self.order - 1][j] * f * self.h
        return y

    def adam_multon(self, i):
        params = (self.t[i], self.y[i])
        self.y[i] = self.runge_kutta(params)
        fs = []

        for j in range(self.order):
            t = self.t[i - j]
            y = self.y[i - j]
            fs.append(self.func(t, y))

        y = self.h * sum([(self.moulton_coeff[self.order - 1][j] * fs[j]) for j in range(self.order)])
        return y

    def inverse(self, i):
        params = (self.t[i], self.y[i])
        self.y[i] = self.runge_kutta(params)
        fs = []

        fs.append(self.h * self.func(self.t[i], self.y[i]))
        for j in range(self.order):
            fs.append(self.y[i - j])

        y = sum([a * t for a, t in zip(self.inverse_coeff[self.order - 1], fs)])
        return y

class Solver:

    def __init__(self, method, steps, ode):
        self.method = method
        self.steps = steps
        self.ode = ode
        self.points = []

    def solve(self):
        if self.method == "euler":
            self.points.append((self.ode.t, self.ode.y))
            for i in range(self.steps):
                self.ode.y = self.ode.euler()
                self.ode.t += self.ode.h
                self.points.append((self.ode.t, self.ode.y))
            return self.points

        elif self.method == "euler_inverso":
            self.points.append((self.ode.t, self.ode.y))
            for i in range(self.steps):
                self.ode.y = self.ode.backward_euler()
                self.ode.t += self.ode.h
                self.points.append((self.ode.t, self.ode.y))
            return self.points

        elif self.method == "euler_aprimorado":
            self.points.append((self.ode.t, self.ode.y))
            for i in range(self.steps):
                self.ode.y = self.ode.modified_euler()
                self.ode.t += self.ode.h
                self.points.append((self.ode.t, self.ode.y))
            return self.points

        elif self.method == "runge_kutta":
            self.points.append((self.ode.t, self.ode.y))
            for i in range(self.steps):
                self.ode.y = self.ode.runge_kutta()
                self.ode.t += self.ode.h
                self.points.append((self.ode.t, self.ode.y))
            return self.points

        elif self.method == "adam_bashforth":
            for i in range(self.ode.order):
                self.points.append((self.ode.t[i], self.ode.y[i]))

            for i in range(self.ode.order, self.steps + 1):
                self.ode.y.append(self.ode.y[i - 1] + self.ode.adam_bashforth(i - 1))
                self.ode.t.append(self.ode.t[i] + self.ode.h)
                self.points.append((self.ode.t[i], self.ode.y[i]))

            return self.points

        elif self.method == "adam_bashforth_by_euler":
            ys = []
            ts = []
            ys.append(self.ode.y)
            ts.append(self.ode.t)
            self.points.append((self.ode.t, self.ode.y))
            for i in range(self.ode.order - 1):
                self.ode.y = self.ode.euler()
                self.ode.t += self.ode.h
                ts.append(self.ode.t)
                ys.append(self.ode.y)
                self.points.append((self.ode.t, self.ode.y))
            self.ode.t = ts
            self.ode.y = ys
            for i in range(self.ode.order, self.steps + 1):
                self.ode.y.append(self.ode.y[i - 1] + self.ode.adam_bashforth(i - 1))
                self.ode.t.append(self.ode.t[i - 1] + self.ode.h)
                self.points.append((self.ode.t[i], self.ode.y[i]))
            return self.points

        elif self.method == "adam_bashforth_by_euler_inverso":
            ys = []
            ts = []
            ys.append(self.ode.y)
            ts.append(self.ode.t)
            self.points.append((self.ode.t, self.ode.y))
            for i in range(self.ode.order - 1):
                self.ode.y = self.ode.backward_euler()
                self.ode.t += self.ode.h
                ts.append(self.ode.t)
                ys.append(self.ode.y)
                self.points.append((self.ode.t, self.ode.y))
            self.ode.t = ts
            self.ode.y = ys
            for i in range(self.ode.order, self.steps + 1):
                self.ode.y.append(self.ode.y[i - 1] + self.ode.adam_bashforth(i - 1))
                self.ode.t.append(self.ode.t[i - 1] + self.ode.h)
                self.points.append((self.ode.t[i], self.ode.y[i]))
            return self.points

        elif self.method == "adam_bashforth_by_euler_aprimorado":
            ys = []
            ts = []
            ys.append(self.ode.y)
            ts.append(self.ode.t)
            self.points.append((self.ode.t, self.ode.y))
            for i in range(self.ode.order - 1):
                self.ode.y = self.ode.modified_euler()
                self.ode.t += self.ode.h
                ts.append(self.ode.t)
                ys.append(self.ode.y)
                self.points.append((self.ode.t, self.ode.y))
            self.ode.t = ts
            self.ode.y = ys
            for i in range(self.ode.order, self.steps + 1):
                self.ode.y.append(self.ode.y[i - 1] + self.ode.adam_bashforth(i - 1))
                self.ode.t.append(self.ode.t[i - 1] + self.ode.h)
                self.points.append((self.ode.t[i], self.ode.y[i]))
            return self.points

        elif self.method == "adam_bashforth_by_runge_kutta":
            ys = []
            ts = []
            ys.append(self.ode.y)
            ts.append(self.ode.t)
            self.points.append((self.ode.t, self.ode.y))
            for i in range(self.ode.order - 1):
                self.ode.y = self.ode.runge_kutta()
                self.ode.t += self.ode.h
                ts.append(self.ode.t)
                ys.append(self.ode.y)
                self.points.append((self.ode.t, self.ode.y))
            self.ode.t = ts
            self.ode.y = ys
            for i in range(self.ode.order, self.steps + 1):
                self.ode.y.append(self.ode.y[i - 1] + self.ode.adam_bashforth(i - 1))
                self.ode.t.append(self.ode.t[i - 1] + self.ode.h)
                self.points.append((self.ode.t[i], self.ode.y[i]))
            return self.points

        elif self.method == "adam_multon":
            for i in range(self.ode.order):
                self.points.append((self.ode.t[i], self.ode.y[i]))

            for i in range(self.ode.order, self.steps + 1):
                self.ode.y.append(self.ode.y[i - 1] + self.ode.adam_multon(i - 1))
                self.ode.t.append(self.ode.t[i] + self.ode.h)
                self.points.append((self.ode.t[i], self.ode.y[i]))

            return self.points

        elif self.method == "adam_multon_by_euler":
            ys = []
            ts = []
            ys.append(self.ode.y)
            ts.append(self.ode.t)
            self.points.append((self.ode.t, self.ode.y))
            for i in range(self.ode.order - 1):
                self.ode.y = self.ode.euler()
                self.ode.t += self.ode.h
                ts.append(self.ode.t)
                ys.append(self.ode.y)
                self.points.append((self.ode.t, self.ode.y))
            self.ode.t = ts
            self.ode.y = ys
            for i in range(self.ode.order, self.steps + 1):
                self.ode.y.append(self.ode.y[i - 1] + self.ode.adam_multon(i - 1))
                self.ode.t.append(self.ode.t[i - 1] + self.ode.h)
                self.points.append((self.ode.t[i], self.ode.y[i]))
            return self.points

        elif self.method == "adam_multon_by_euler_inverso":
            ys = []
            ts = []
            ys.append(self.ode.y)
            ts.append(self.ode.t)
            self.points.append((self.ode.t, self.ode.y))
            for i in range(self.ode.order - 1):
                self.ode.y = self.ode.backward_euler()
                self.ode.t += self.ode.h
                ts.append(self.ode.t)
                ys.append(self.ode.y)
                self.points.append((self.ode.t, self.ode.y))
            self.ode.t = ts
            self.ode.y = ys
            for i in range(self.ode.order, self.steps + 1):
                self.ode.y.append(self.ode.y[i - 1] + self.ode.adam_multon(i - 1))
                self.ode.t.append(self.ode.t[i - 1] + self.ode.h)
                self.points.append((self.ode.t[i], self.ode.y[i]))
            return self.points

        elif self.method == "adam_multon_by_euler_aprimorado":
            ys = []
            ts = []
            ys.append(self.ode.y)
            ts.append(self.ode.t)
            self.points.append((self.ode.t, self.ode.y))
            for i in range(self.ode.order - 1):
                self.ode.y = self.ode.modified_euler()
                self.ode.t += self.ode.h
                ts.append(self.ode.t)
                ys.append(self.ode.y)
                self.points.append((self.ode.t, self.ode.y))
            self.ode.t = ts
            self.ode.y = ys
            for i in range(self.ode.order, self.steps + 1):
                self.ode.y.append(self.ode.y[i - 1] + self.ode.adam_multon(i - 1))
                self.ode.t.append(self.ode.t[i - 1] + self.ode.h)
                self.points.append((self.ode.t[i], self.ode.y[i]))
            return self.points

        elif self.method == "adam_multon_by_runge_kutta":
            ys = []
            ts = []
            ys.append(self.ode.y)
            ts.append(self.ode.t)
            self.points.append((self.ode.t, self.ode.y))
            for i in range(self.ode.order - 1):
                self.ode.y = self.ode.runge_kutta()
                self.ode.t += self.ode.h
                ts.append(self.ode.t)
                ys.append(self.ode.y)
                self.points.append((self.ode.t, self.ode.y))
            self.ode.t = ts
            self.ode.y = ys
            for i in range(self.ode.order, self.steps + 1):
                self.ode.y.append(self.ode.y[i - 1] + self.ode.adam_multon(i - 1))
                self.ode.t.append(self.ode.t[i - 1] + self.ode.h)
                self.points.append((self.ode.t[i], self.ode.y[i]))
            return self.points

        elif self.method == "formula_inversa":
            for i in range(self.ode.order):
                self.points.append((self.ode.t[i], self.ode.y[i]))

            for i in range(self.ode.order, self.steps + 1):
                self.ode.y.append(self.ode.y[i - 1] + self.ode.inverse(i - 1))
                self.ode.t.append(self.ode.t[i] + self.ode.h)
                self.points.append((self.ode.t[i], self.ode.y[i]))

            return self.points

        elif self.method == "formula_inversa_by_euler":
            ys = []
            ts = []
            ys.append(self.ode.y)
            ts.append(self.ode.t)
            self.points.append((self.ode.t, self.ode.y))
            for i in range(self.ode.order - 1):
                self.ode.y = self.ode.euler()
                self.ode.t += self.ode.h
                ts.append(self.ode.t)
                ys.append(self.ode.y)
                self.points.append((self.ode.t, self.ode.y))
            self.ode.t = ts
            self.ode.y = ys
            for i in range(self.ode.order, self.steps + 1):
                self.ode.y.append(self.ode.y[i - 1] + self.ode.inverse(i - 1))
                self.ode.t.append(self.ode.t[i - 1] + self.ode.h)
                self.points.append((self.ode.t[i], self.ode.y[i]))
            return self.points

        elif self.method == "formula_inversa_by_euler_inverso":
            ys = []
            ts = []
            ys.append(self.ode.y)
            ts.append(self.ode.t)
            self.points.append((self.ode.t, self.ode.y))
            for i in range(self.ode.order - 1):
                self.ode.y = self.ode.backward_euler()
                self.ode.t += self.ode.h
                ts.append(self.ode.t)
                ys.append(self.ode.y)
                self.points.append((self.ode.t, self.ode.y))
            self.ode.t = ts
            self.ode.y = ys
            for i in range(self.ode.order, self.steps + 1):
                self.ode.y.append(self.ode.y[i - 1] + self.ode.inverse(i - 1))
                self.ode.t.append(self.ode.t[i - 1] + self.ode.h)
                self.points.append((self.ode.t[i], self.ode.y[i]))
            return self.points

        elif self.method == "formula_inversa_by_euler_aprimorado":
            ys = []
            ts = []
            ys.append(self.ode.y)
            ts.append(self.ode.t)
            self.points.append((self.ode.t, self.ode.y))
            for i in range(self.ode.order - 1):
                self.ode.y = self.ode.modified_euler()
                self.ode.t += self.ode.h
                ts.append(self.ode.t)
                ys.append(self.ode.y)
                self.points.append((self.ode.t, self.ode.y))
            self.ode.t = ts
            self.ode.y = ys
            for i in range(self.ode.order, self.steps + 1):
                self.ode.y.append(self.ode.y[i - 1] + self.ode.inverse(i - 1))
                self.ode.t.append(self.ode.t[i - 1] + self.ode.h)
                self.points.append((self.ode.t[i], self.ode.y[i]))
            return self.points

        elif self.method == "formula_inversa_by_runge_kutta":
            ys = []
            ts = []
            ys.append(self.ode.y)
            ts.append(self.ode.t)
            self.points.append((self.ode.t, self.ode.y))
            for i in range(self.ode.order - 1):
                self.ode.y = self.ode.runge_kutta()
                self.ode.t += self.ode.h
                ts.append(self.ode.t)
                ys.append(self.ode.y)
                self.points.append((self.ode.t, self.ode.y))
            self.ode.t = ts
            self.ode.y = ys
            for i in range(self.ode.order, self.steps + 1):
                self.ode.y.append(self.ode.y[i - 1] + self.ode.inverse(i - 1))
                self.ode.t.append(self.ode.t[i - 1] + self.ode.h)
                self.points.append((self.ode.t[i], self.ode.y[i]))
            return self.points

if __name__ == "__main__":

    # Defining equation symbols
    t, y = sympy.symbols("t y")

    # Reading input file
    inFile = open("entrada.txt", "r")
    # outFile = open("saida.txt", "w")
    lines = inFile.readlines()

    for line in lines:
        words = line.split()

        method = words[0]

        if len(words) > 7:
            order = int(words[-1])
            f = sympy.lambdify([t, y], sympy.sympify(words[-2]), "math")
            steps = int(words[-3])
            h = float(words[-4])
            t0 = float(words[-5])
            ts = []
            ts.append(t0)
            ys = []
            for i in range(order):
                ys.append(float(words[i + 1]))
                ts.append(ts[i] + h)
            problem = ODE(ys, ts, h, f, order)
        else:
            y0 = float(words[1])
            t0 = float(words[2])
            h = float(words[3])
            steps = int(words[4])
            # Converts the function string into a lambda function
            f = sympy.lambdify([t, y], sympy.sympify(words[5]), "math")
            if len(words) == 7:
                order = int(words[6])
                problem = ODE(y0, t0, h, f, order)
            elif len(words) < 7:
                problem = ODE(y0, t0, h, f)

        calc = Solver(method, steps, problem)
        print(method)
        points = calc.solve()
        print("step, \tt, \ty")
        for i in range(len(points)):
            print(i, " ", points[i])

        pyplot.title(method)
        pyplot.xlabel('t')
        pyplot.ylabel('y')
        pyplot.plot(*zip(*points))
        pyplot.show()


import pandas as pd
import numpy as np
import math

'''
rmse of 2 series
'''
def rmse(y, pred):
  d = np.linalg.norm(y - pred)
  return d / np.sqrt(len(pred))

def module(x, y, z):
  return math.sqrt(x*x+y*y+z*z)

def comps2module(X, Y, Z):
  result = []
  for x,y,z in zip(X,Y,Z):
    result.append(module(x,y,z))
  return np.array(result)

def autocorr(x):
  result = np.correlate(x, x, mode='full')
  return np.array(result[len(result)//2:])

def corr(x,y):
  result = np.correlate(x, y, mode='full')
  return np.array(result[len(result)//2:])

def freq_from_autocorr(x):
    """Estimate frequency using autocorrelation

    Pros: Best method for finding the true fundamental of any repeating wave,
    even with strong harmonics or completely missing fundamental

    Cons: Not as accurate, currently has trouble with finding the true peak

    """
    corr = autocorr(x)

    # Find the first low point
    d = np.diff(corr)
    start = np.where(d>0)[0]

    if len(start)>0 :
      return np.argmax(corr[start[0]:]) + start[0]
    return 0

    # Find the next peak after the low point (other than 0 lag).  This bit is
    # not reliable for long signals, due to the desired peak occurring between
    # samples, and other peaks appearing higher.
    # Should use a weighting function to de-emphasize the peaks at longer lags.
    # Also could zero-pad before doing circular autocorrelation.
    #peak = np.argmax(corr[start:])
    #px, py = parabolic(corr, peak)

    #print("minimum at {}, peak at {}".format(start, peak + start))

    #return fs / px
    #return peak + start


import numpy as np
import os
import math as m
import matplotlib.pyplot as plt
from task2ab import save_im

def convolve_im(im, kernel):
    """ A function that convolves im with kernel

    Args:
        im ([type]): [np.array of shape [H, W, 3]]
        kernel ([type]): [np.array of shape [K, K]]

    Returns:
        [type]: [np.array of shape [H, W, 3]. should be same as im]
    """
    # Creating output matrix
    convolved_im = np.zeros_like(im)

    # Flip kernel
    kernel = np.flipud(np.fliplr(kernel))

    # Getting the dimensions of the im matrix
    im_rows = im.shape[0]
    im_cols = im.shape[1]

    # Getting the dimensions of the kernel matrix
    kernel_rows = kernel.shape[0]
    kernel_cols = kernel.shape[1]

    # Defining center assuming kernel_rows and kernel_cols are odd numbers
    kernel_center_x = m.floor(kernel_cols/2)
    kernel_center_y = m.floor(kernel_rows/2)

    # Pad the image with the appropriate amount of rows and cols
    image_padded = np.zeros((im_rows + (kernel_rows-1), im_cols + (kernel_cols-1), 3))

    image_padded[kernel_center_y:-kernel_center_y, kernel_center_x:-kernel_center_x, 0] = im[:, :, 0]
    image_padded[kernel_center_y:-kernel_center_y, kernel_center_x:-kernel_center_x, 1] = im[:, :, 1]
    image_padded[kernel_center_y:-kernel_center_y, kernel_center_x:-kernel_center_x, 2] = im[:, :, 2]

    # Iterating through all the cells of the image, convolving with the kernel and placing correctly in the convolved image
    for col in range(im_cols):
        for row in range(im_rows):
            convolved_im[row, col, 0] = (kernel * image_padded[row:row+kernel_rows, col:col+kernel_cols, 0]).sum()
            convolved_im[row, col, 1] = (kernel * image_padded[row:row+kernel_rows, col:col+kernel_cols, 1]).sum()
            convolved_im[row, col, 2] = (kernel * image_padded[row:row+kernel_rows, col:col+kernel_cols, 2]).sum()

    return convolved_im

if __name__ == "__main__":
    # Read image
    impath = os.path.join("images", "lake.jpg")
    im = plt.imread(impath)

    # Define the convolutional kernels
    h_a = np.ones((3, 3)) / 9
    h_b = np.array([
        [1, 4, 6, 4, 1],
        [4, 16, 24, 16, 4],
        [6, 24, 36, 24, 6],
        [4, 16, 24, 16, 4],
        [1, 4, 6, 4, 1],
    ]) / 256
    # Convolve images
    smoothed_im1 = convolve_im(im.copy(), h_a)
    smoothed_im2 = convolve_im(im, h_b)

    # DO NOT CHANGE
    assert isinstance(smoothed_im1, np.ndarray), \
        f"Your convolve function has to return a np.array. " +\
        f"Was: {type(smoothed_im1)}"
    assert smoothed_im1.shape == im.shape, \
        f"Expected smoothed im ({smoothed_im1.shape}" + \
        f"to have same shape as im ({im.shape})"
    assert smoothed_im2.shape == im.shape, \
        f"Expected smoothed im ({smoothed_im1.shape}" + \
        f"to have same shape as im ({im.shape})"

    save_im("convolved_im_h_a.jpg", smoothed_im1)
    save_im("convolved_im_h_b.jpg", smoothed_im2)


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

# # Computação vetorizada

# ## Objetivos
#
# - Compreender as capacidades da computação vetorizada;
# - Associar conceitos abstratos de Matemática a estruturas computacionais;
# - Saber estruturar dados em arrays multidimensionais;

# ## Introdução
#
# - Computação científica, problemas de alta complexidade e recursos computacionais.
# - Resolução eficiente de problemas na ciência de dados
# - Computação vetorizada (arrays x listas)

# ## O pacote *numpy*
#
# - Biblioteca padrão em Python para trabalhar com *arrays* multidimensionais e computação vetorizada.
# - Superpoderes às listas
# > A grande regra: _vetorize seus cálculos numéricos o máximo possível!_

# In[1]:

import numpy as np

# ## Motivação
#
# Este exemplo compara a eficiência de operações feitas com listas comuns e com *numpy*.

# In[2]:

# 1 µs = 1/1e6 segundo
# 1 ns = 1/1e9 segundo

L = range(500)
get_ipython().run_line_magic('timeit', '-n 10 [i**2 for i in L] # executa o laço 10 vezes')

a = np.arange(500)
get_ipython().run_line_magic('timeit', '-n 10 a**2 # eleva ao quadrado diretamente 10 vezes')

# ## Criação de arrays unidimensionais (1D)

# In[3]:

a = [1,2,3]
np.array(a) # a partir de lista

# In[4]:

np.array([1,2,3]) # diretamente

# In[5]:

np.array([2]*5)

# ## Criação de arrays bidimensionais (2D)

# In[6]:

A = [ [1,2], [0,2] ] # lista de listas
np.array(A) # matrix 2 x 2

# In[7]:

np.array([ [1,2], [0,2] ]) # diretamente

# In[8]:

A2 = [[1,2,3],[4,3,2]] # cada lista é uma linha da matriz
np.array(A2) # matriz 2 x 3

# In[9]:

np.array([1,1],[0,1]) # parênteses externos são obrigatórios!

# ### Dimensão, formato e comprimento

# In[11]:

x = np.array(a)
np.ndim(x) # aplica a função ndim

# In[12]:

x.ndim # como método

# In[13]:

np.shape(x) # formato

# In[14]:

x.shape # sem parênteses

# In[15]:

len(x) # comprimento

# In[16]:

X = np.array(A)
np.ndim(X) # array bidimensional

# In[17]:

np.shape(X)

# In[18]:

len(X) # apenas um comprimento. Qual?

# In[19]:

X2 = np.array(A2)
len(X2) # apenas da primeira dimensão. LINHAS

# In[20]:

X2.shape

# ## Funções para criação de arrays

# ### `arange`
#
# **Exemplo:** crie um array de números ímpares positivos menores do que 36.

# In[21]:

np.arange(1,36,2) # start,stop,step

# **Exemplo:** crie um array de números pares positivos menores ou iguais a 62.

# In[22]:

np.arange(0,63,2)

# **Exemplo:** calcule o valor de $f(x) = x^3 + 2x$ para $x$ elementos dos arrays anteriores.

# In[23]:

imp, par = np.arange(1,36,2), np.arange(0,63,2)
f = lambda x: x**3 + 2
fi, fp = f(imp), f(par)
print(fi)
print(fp)

# ### `linspace`
#
# **Exemplo:** crie um array igualmente espaçado de elementos em [0,1] com 11 elementos.

# In[24]:

np.linspace(0,1,num=11)

# In[25]:

np.linspace(0,1,11) # 'num' pode ser omitido

# In[26]:

x = np.linspace(0,1,10,endpoint=False) # exclui último
x

# In[27]:

y = np.arange(0,1,0.1) # equivalente
y

# In[28]:

x == y # comparação é elemento a elemento

# In[29]:

x == -y # apenas 0 é True

# In[30]:

x[1:] == y[1:]

# ### `all` e `any`

# In[31]:

np.all( x == y ) # verifica se todos são 'True'

# In[32]:

np.any( x == -y ) # verifica se pelo menos um é 'True'

# ### `random`
#
# **Exemplo**: crie um *array* 1D com 5 números aleatórios entre [0,1].

# In[33]:

r = np.random.rand(5)
r

# **Exemplo**: crie um *array* 1D com 50 números inteiros aleatórios entre [0,7].

# In[34]:

r2 = np.random.randint(0,7+1,50) # menor, maior é 8 (exclusive), tamanho
r2

# **Exemplo**: crie uma matriz m x n com números inteiros aleatórios entre inteiros [l,h].

# In[35]:

def gera_matriz(m,n,l,h):
    return np.random.randint(l,h,(m,n)) # tupla (m,n)

# In[36]:

gera_matriz(2,2,0,4)

# In[37]:

gera_matriz(3,2,0,4)

# In[38]:

gera_matriz(4,4,-2,7)

# ### `ones`
#
# Criando arrays unitários.

# In[39]:

np.ones(4)

# In[40]:

np.ones((3,2)) # tupla necessária para linhas e colunas

# ### `eye`
#
# Criando arrays 2D identidade. 1 na diagonal e 0 nas demais.

# In[41]:

np.eye(3) # matriz identidade 3 x 3

# ### `zeros`
#
# Arrays nulos.

# In[42]:

np.zeros(3)

# In[43]:

np.zeros((2,4)) # 2 x 4

# ### `full`
#
# Arrays de valor constante.

# In[44]:

np.full(3,0) # 1 x 3 com constante 0

# In[45]:

np.full(shape=(3,),fill_value=0)

# In[46]:

F1 = np.full(shape=(2,2),fill_value=1) # 2 x 2 com 1
F1

# In[47]:

F1 == np.ones(2) # mesmo resultado que ones

# Outras maneiras:

# In[48]:

F2 = 3*np.ones((4,4))
F2

# ## Especificando tipos de dados
#
# Observe o seguinte exemplo:

# In[49]:

F2

# In[50]:

F3 = np.full((4,4),3)
F3

# In[51]:

F2 == F3 # valores iguais

# In[52]:

F2.dtype == F3.dtype # tipos diferentes

# In[53]:

F2.dtype

# In[54]:

F3.dtype

# Especificamos o tipo de dados com `dtype`.

# In[55]:

np.ones((4,2),dtype=bool) # matriz de booleanos

# In[56]:

np.ones((4,2),dtype=str) # matriz de strings; 'U1' diz que há no máximo 1 caracter

# In[57]:

S = np.array(['dias','mes','ano'])
S.dtype # 4 é o no. máximo de caracteres nas strings

# ## Indexação e fatiamento
#
# Funcionam de maneira similar ao uso com listas.

# In[58]:

I = np.linspace(0,20,11)
I

# In[59]:

I[3],I[2:4],I[5:8],I[-4:-1]

# In[60]:

I[::-1] # invertendo o array

# In[61]:

I2 = np.array([I,2*I,3*I,4*I])
I2

# Em arrays bidimensionais, a indexação é feita por meio de uma tupla. Porém, a explicitação dos parênteses é desnecessária.

# In[62]:

I2[(2,3)] # 3a. linha; 4a. coluna

# In[63]:

I2[2,3]

# In[64]:

I2[0,:] # 1a. linha

# In[65]:

I2[1,:] # 2a. linha

# In[66]:

I2[-1,:] # última linha

# In[67]:

I2[:,0] # 1a. coluna

# In[68]:

I2[:,1] # 2a. coluna

# In[69]:

I2[:,8] # 9a. coluna

# In[70]:

I2[:,2:4] # 3a. - 4a. coluna

# In[71]:

I2[1:3,6:10] # submatriz: linhas 2 - 3; 7-10

# ### Alteração de valores
#
# Os arrays são mutáveis por indexação.

# In[72]:

A3 = np.random.rand(4,4)
A3

# In[73]:

A3[0:4,0] = -1
A3

# In[74]:

A3[:,-1] = -1
A3

# In[75]:

A3[1:3,1:3] = 0
A3

# Podemos alterar valores com arrays.

# In[76]:

A3[1,:] = -2*np.ones(4)
A3

# A indexação pode usar um comprimento de passo (*step*).

# In[77]:

A3[0:4:3,1:3] = np.full((1,2),8) # na indexação esquerda, 1a. linha : 4a. linha : step de 3
A3

# ### `newaxis`
#
# `newaxis` é uma instância do `numpy` que permite aumentar de 1 a dimensão de um array existente.

# **Exemplo:** como inserir a diagonal de uma matriz em uma segunda matriz como uma coluna adicional?

# Criamos duas matrizes aleatórias.

# In[78]:

# matriz 4 x 4 de inteiros aleatórios entre 0 e 9
B1 = np.random.randint(0,10,(4,4))
B1

# In[79]:

# matriz 4 x 4 de inteiros aleatórios entre -10 e 9
B2 = np.random.randint(-10,10,(4,4))
B2

# Extraímos a diagonal da primeira.

# In[80]:

# diagonal de B1
db1 = np.diag(B1)
db1

# Notemos agora que as dimensões são diferentes.

# In[81]:

print(B2.ndim)
print(db1.ndim)

# Para podermos aglutinar a diagonal como uma nova coluna na primeira matriz, primeiro temos que transformar o array unidimensional para uma matriz.

# In[82]:

db1 = db1[:,np.newaxis]
print(db1.ndim) # agora o array é bidimensional
db1

# `newaxis` é um "eixo imaginário" incluído *inplace*, mas que altera dinamicamente o array. No caso acima, o array tornou-se em uma coluna.

# Agora, podemos "colar" um array 2D com outro por uma concatenação.

# ### `concatenate`
#
# `concatenate` é usado para concatenar *arrays*. A concatenação requer uma tupla contendo os *arrays* a concatenar e o eixo de referência.

# In[83]:

B3 = np.concatenate((B2,db1), axis=1)
B3

# No caso acima, `axis=1` indica que a concatenação é ao longo da coluna. Dessa forma, inserimos a segunda diagonal como uma coluna adicional na segunda matriz. Claramente, isto só é possível porque ambas as matrizes eram de mesmo formato.

# #### `axis`
#
# Nos arrays multidimensionais do Python, `axis` é usado para indicar a "direção" dos dados. Em arrays bidimensionais, `axis=0` refere-se à direção de cima para baixo (ao longo das linhas), ao passo que `axis=1` refere-se à direção da esquerda para a direita (ao longo das colunas).
#
# **Obs.:** note que a palavra `axis` ("eixo") deve ser usada, e não "axes" ("eixos").

# Para aglutinar uma linha na matriz anterior, fazemos uma concatenação em linha.

# In[84]:

# array de zeros com mesmo número de colunas de B3
db2 = np.zeros(np.shape(B3)[1])
db2

# In[85]:

db2 = db2[np.newaxis,:] # cria o "eixo imaginário" na direção 0

# In[86]:

B4 = np.concatenate((B3,db2),axis=0) # concatena ao longo das linhas
B4

# ## Indexação avançada
#
# Podemos usar máscaras como artifícios para indexação avançada.

# In[87]:

IA1 = np.arange(-10,11)
IA1

# Vamos criar um *array* aleatório de True e False no mesmo formato que o *array* anterior.

# In[88]:

mask1 = np.random.randint(0,2,np.shape(IA1),dtype=bool)
mask1

# Esta *máscara booleana* pode ser aplicada no array para extrair apenas os elementos cujos índices são marcados como `True` pela máscara.

# In[89]:

IA1[mask1]

# Há maneiras mais diretas aplicáveis a filtragens. Para extrair os valores negativos do array:

# In[90]:

IA1 < 0 # máscara booleana

# In[91]:

IA1[IA1 < 0]

# Para extrair os valores positivos do array:

# In[92]:

IA1[IA1 > 0] # máscara booleana para positivos

# Para extrair os valores no intervalo $]-2,5[$, fazemos:

# In[93]:

IA1[(IA1 > -2) & (IA1 < 5)] # & é o operador booleano 'elemento a elemento'

# Para extrair pares e ímpares, poderíamos fazer:

# In[94]:

pares, impares = IA1[IA1 % 2 == 0] , IA1[IA1 % 2 == 1]
pares,impares

# Podemos usar listas como máscaras:

# In[95]:

alguns = pares[[0,2,3,5]] # acessa 1o., 3o. 4o. e 6o. elemento de 'pares'

# In[96]:

impares[alguns] # estude este caso

# No caso acima, por exemplo, -10 é uma indexação reversa que excede o compriemento do array à esquerda. Portanto, ele retorna o primeiro elemento do array, que é -9. O índice -6 acessa o sexto elemento a partir da direita, que é -1. O índice -4 acessa o quarto elemento a partir da direita. Por fim, o índice 0 acessa o primeiro elemento que é -9.

# ## Operações elemento a elemento
#
# As operações aritméticas e de cálculo são feitas elemento a elemento nos *arrays*. Já mostramos alguns exemplos acima, mas vamos tornar isto mais claro aqui.

# In[97]:

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

# In[98]:

# operações elemento a elemento
print(a + b)
print(a - b)
print(a * b)
print(a / b)
print(a ** b)

# In[99]:

2*a + 4*b - 6*b**2 + 1.1/2*a

# ## Funções matemáticas
#
# O `numpy` possui a maioria dass funções disponíveis no módulo `math` e outras mais. As funções são diretamente aplicáveis aos *arrays*. Lembre-se que para fazer o mesmo usando em listas, tínhamos de construir meios de iterar sobre elas e aplicar a função a cada elemento por vez. Isto não é mais necessário com o `numpy`. Eis a beleza da computação vetorizada!
#
# Vejamos uma série de exemplos.

# In[100]:

x = np.arange(10)
x

# In[101]:

np.sqrt(x)

# In[102]:

np.cos(x) + 2*np.sqrt(x)

# In[103]:

y = np.sin(2*x)
z = np.exp(x + y)
y - z

# ## *Broadcasting*
#
# *Broadcasting* é a capacidade que o *numpy* oferece para realizarmos operações em arrays com diferentes dimensões.

# ### Regras do *broadcasting*
#
# 1. Se dois *arrays* tiverem dimensões diferentes, o formato do array com menor dimensão é preenchido por 1 do lado esquerdo.
# 2. Se o formato dos *arrays* não for igual em dimensão alguma, o array com tamanho igual a 1 é esticado nesta direção para ficar no mesmo tamanho correspondente do outro array.
# 3. Se em qualquer direção os tamanhos dos *arrays* forem diferentes e nenhum deles for igual a 1, então um erro é retornado.

# #### Exemplo da Regra 1

# In[104]:

A = np.array([[1, 2, 3],[4, 5, 6]]) # array 2D
b = np.array([10, 20, 30]) # array 1D
print(A.shape)
print(b.shape)

# In[105]:

A + b

# A soma pode ser realizada mesmo assim. O que ocorreu? Cada linha de `A` foi somada à única linha de `b`. O *broadcasting* amplia o array de menor dimensão automaticamente da seguinte forma:
#
# Pela regra 1, o *array* `b` tem dimensão menor. Então, ele é preenchido de modo que:
#
# ```python
# A.shape -> (2, 3)
# b.shape -> (1, 3)
# ```
#
# Pela regra 2, a primeira dimensão de `A` é 2 e a de `b` é 1. Então, a dimensão de `b` é "esticada", de modo que:
#
# ```python
# A.shape -> (2, 3)
# b.shape -> (2, 3)
# ```

# A mesma operação poderia ter sido feita com:

# In[106]:

A + np.array([b,b])

# #### Exemplo da Regra 2

# In[107]:

A = np.arange(3).reshape((3, 1))
b = np.arange(3)
print(A.shape)
print(b.shape)

# In[108]:

A + b

# Neste caso, ambos os arrays sofrem *broadcasting*. Ele ocorre da seguinte forma.
#
# Como
#
# ```python
# A.shape = (3, 1)
# b.shape = (3,)
# ```
# a regra 1 diz que `b` deve ser preenchido de modo que
#
# ```python
# A.shape -> (3, 1)
# b.shape -> (1, 3)
# ```
# e, pela regra 2, cada uma das dimensões 1 deve ser alterada de modo que:
#
# ```python
# A.shape -> (3, 3)
# b.shape -> (3, 3)
# ```
#
# Assim, o *broadcasting* é permitido.

# #### Exemplo da Regra 3

# In[109]:

A = np.ones((3, 2))
b = np.arange(3)
print(A.shape)
print(b.shape)

# In[110]:

A + b

# Neste exemplo, o *broadcasting* não é permitido. O caso é levemente diferente do primeiro exemplo em que `A` é transposta.
#
# Temos que
#
# ```python
# M.shape = (3, 2)
# a.shape = (3,)
# ```
#
# Pela regra 1, devemos ter
#
# ```python
# M.shape -> (3, 2)
# a.shape -> (1, 3)
# ```
#
# e, pela regra 2, a primeira dimensão deve ser esticada para combinar-se com a de `A` enquanto a segunda não é alterada por não ser 1.
#
# ```python
# M.shape -> (3, 2)
# a.shape -> (3, 3)
# ```
# Porém, o formato final de ambos não se combina. Sendo incompatíveis, o *broadcasting* falha.


### Author: Khoong Wei Hao
### Project 2

import numpy as np
import imageio
from scipy.fftpack import dct, idct
from scipy.misc import toimage
import math
import time
from collections import namedtuple
from matplotlib import pyplot as plt

# the following functions phi(n), idct2_1d(data), idct2_2d(dct_output) are from Project 1, for the 2D iDCT
def phi(n):
    if n == 0:
        return (1/2)**(1/2)
    else:
        return 1

def idct2_1d(data):
    N = data.shape[0]
    B = np.zeros([N, N])
    l = np.zeros([N])
    for i in range(N):
        for j in range(N):
            B[i, j] = ((2/N)**(1/2)) * phi(j) * np.cos((i + 1/2) * np.pi * j / N)
    return np.dot(B, data)

def idct2_2d(dct_output):
    start_time = time.time()
    # note that the image matrix is square, so N is the number of rows/columns
    N = dct_output.shape[0]
    d = np.empty([N, N], float)
    x = np.empty([N, N], float)
    for i in range(N):
        d[:,i] = idct2_1d(dct_output[:,i])
    for i in range(N):
        x[i,:] = idct2_1d(d[i,:])

    total_time_taken = time.time() - start_time
    print('Total time taken = ' + str(total_time_taken) + 's')
    return x

###########################################
### Project 2 Functions For imdecomp.py ###
###########################################

# function to partition image into 8 X 8 blocks
def partition_img(img, M, N):
    num_partitions_by_row = M/8
    num_partitions_by_col = N/8

    img_r = np.array(np.split(img, num_partitions_by_row, axis=0))
    img_rc = np.array(np.split(img_r, num_partitions_by_col, axis=2))
    img_final = np.concatenate(img_rc, axis=0)

    return img_final

# function to carry out 2D iDCT for each 8X8 patch
def idct2d_8_by_8(patches):
    return [idct2_2d(p) for p in patches]

# function to construct image J by concatenating all 8X8 patches and convert all entries to unsigned 8-bit integers
def reconstruct_image(idct2d_patches):
    coeff_matrices_decomp_3d = np.array(idct2d_patches)

    # note that each 'row' has 256/8=32 8X8 matrices
    concat_matrices_decomp = []
    for i in range(0,len(idct2d_patches),32):
        # concatenate column-wise
        concat_matrices_decomp += [np.concatenate(idct2d_patches[i:i+32], axis=1)]

    # stack the 32 'rows' of 8X8 matrices to get 256X256
    stack_result_decomp = np.vstack(concat_matrices_decomp)

    # convert all entries to unsigned 8-bit integers
    return np.uint8(stack_result_decomp)

# perform image decompression
def imdecomp(C):
    M = C.shape[0]
    N = C.shape[1]

    # partition compression output into 8X8 patches
    partitions_comp = partition_img(C, M, N)

    # apply 2D iDCT on each 8X8 patch
    idct2d_partitions = idct2d_8_by_8(partitions_comp)

    # reconstruct image by concatenating all 8X8 patches and converting all entries to 8-bit unsigned integers
    reconstructed_image = reconstruct_image(idct2d_partitions)

    return reconstructed_image


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

plt.close()
#Create plots
fig, (ax0,ax1) = plt.subplots(2,1,sharex=True, figsize=(10,10))
lower,upper = -5,5
#Plot r v h as bifurcation.
x = np.linspace(-10,10,200)
r = np.linspace(lower,upper,200,dtype=complex)
h = np.sqrt(r/3)*r*2/3
ax0.set_xlim(lower,upper)
ax0.plot(r,h,'b.',r,-h,'g.', alpha = .3)
ax0.set_title(r'$r$ = $\frac{2r}{3}\sqrt{\frac{r}{3}}$')
ax0.set_ylabel('h')
ax0.grid(True)
#Plot y as function of r and x
ax1.set_ylim(-4,4)
ax1.set_title(r'$y$ = $rx - x^3:   \   \{r \in{}: -5 ... 5\}$')
ax1.set_ylabel('y')
for i in range(lower,upper):
    y = i*x - x**3
    ax1.plot(-y,x, alpha = .4, label='r = {}'.format(i))
    plt.legend()
#3D
fig = plt.figure(figsize=(10,10))
ax2 = fig.gca(projection='3d')

# Make data.
x = np.arange(-1, 1, 0.01)
r = np.arange(-1, 1, 0.01)
X, R = np.meshgrid(x, r)
Z = X*R - X**3
# Plot the surface.
ax2.set_aspect('equal')
ax2.plot_surface(X, R, Z,
                        rstride=5,cstride=5,
                        cmap=cm.inferno,
                        linewidth=1,
                        antialiased=True,
                        alpha=.5)
cset = ax2.contour(X, R, Z, zdir='z',
                            offset=-2.0,
                            cmap=cm.coolwarm,
                            lw=2,
                            tight=True)
#cset = ax2.contour(X, R, Z, zdir='x', offset=0, cmap=cm.coolwarm)
cset = ax2.contour(X, R, Z, zdir='y',  offset=1, cmap=cm.coolwarm)

# Customize the z axis.
#ax2.set_zlim3d(-500, 500)
ax2.set_title('Cusp Catastrophe',size = 16)
ax2.set_xlabel('$x$', size=14)
ax2.set_ylabel('$r$',size=14)
ax2.set_zlabel('$y = r x - x^3 = -h $',size=14)
ax2.set_xticks([0])
ax2.set_yticks([0])
ax2.set_zticks([0])
ax2.grid(True)
ax2.xaxis.pane.set_edgecolor(None)
ax2.yaxis.pane.set_edgecolor(None)
ax2.zaxis.pane.set_edgecolor(None)
ax2.xaxis.pane.fill = False
ax2.yaxis.pane.fill = False
ax2.zaxis.pane.fill = True
#Rotate the plot
# for angle in range(0, 360):
#     ax2.view_init(30, angle)
#     plt.draw()
#     plt.pause(.001)
plt.show()


from math import cos, tanh
import matplotlib.pyplot as plt	# Субмодуль для создания графиков
from numpy import arange  # Импортируем субмодуль создания массива

# Реализация метода пассивного поиска
def PassiveSearch(f, a, b, Nmax):

	print("Метод пассивного поиска")
	print("+-----+------------+")
	print('|  N  |    xMin    |')
	print("+-----+------------+")

	N = 1               # Стартовая точка
	inaccuracy = []     # Пока что пустой список неточностей
	N_list = []         # Пока что пустой список точек N

# Цикл для нахождения минимума
	while N <= Nmax:
		accuracy = (b - a) / (N + 1)       	# Шаг поиска
		inaccuracy.append(accuracy)       		  	# Добавляем неточность
		N_list.append(N)            		# Добавляем N
		xMin = a                    		# Считаем, что минимум находится в точке a
		x = a
		# Идём по всему интервалу
		while x <= b:
			if f(x) < f(xMin):  # Если очередное значение стало меньше,
				xMin = x        # то обновляем точку минимума
			x += accuracy              # Переходим на следующую точку
		print('| %*d | %*.4f |' % (3, N, 10, xMin))
		N += 1                  # Увеличиваем итерацию на 1
		# N *= 2                # Для ускорения нахождения минимума, т.е. каждый раз умножая на 2
	print("+-----+------------+")

# Рисуем график зависимостей неточности от числа точек N
	plt.plot(N_list, inaccuracy)    # Формируем график по осям
	plt.ylabel('Погрешность')       # Подписываем ось У
	plt.xlabel('N')                 # Подписываем ось Х
	plt.grid(True)					# Включаем сетку
	plt.show()                      # Оторбажаем график

	return xMin                     # Возвращщаем конечную точку минимума

# Реализация метода дихотомии
def Dichotomy(f, a, b, eps):
	delta = 0.01
	k = 1

	print("Метод дихотомии")
	print("+-----+------------+------------+------------+------------+------------+------------+------------+")
	print("|  k  |     ak     |     bk     |     lk     |     x1     |     x2     |   f(x1)    |    f(x2)   |")
	print("+-----+------------+------------+------------+------------+------------+------------+------------+")

	# Запускаем цикл. Пока интервал не сомкнётся...
	while b - a > eps:
		x = (a + b) / 2

		x_Left = x - delta
		x_Right = x + delta

		func_xLeft = f(x_Left)
		func_xRight = f(x_Right)

		print('| %*d | %*.3f | %*.3f | %*.3f | %*.3f | %*.3f | %*.3f | %*.3f |' % (3, k, 10, a, 10, b, 10, b - a, 10, x_Left, 10, x_Right, 10, func_xLeft, 10, func_xRight))
		# Выполняем процедуру исключения отрезка
		if func_xLeft >= func_xRight:
			a = x
		else:
			b = x

		k += 1

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

	return (a + b) / 2      # Возвращает вычисленный минимум

# Заданная унимодальная функция для поиска минимума
if __name__ == "__main__":
	UnimodalFunction = lambda x: cos(x) * tanh(x)

# Интервал поиска [-2, 0]
	a = -2
	b = 0
	eps = 0.1                   # Точность
	Nmax = (2*(b-a)/eps - 1)    # Максимально возможное число точек
	delta = 0.01
	xlist = arange(a, b, delta)
	ylistUni = [UnimodalFunction(x) for x in xlist]

	plt.plot(xlist, ylistUni)
	plt.title("Унимодальная функция")
	plt.grid(True)
	plt.show()

	xMin_ps = PassiveSearch(UnimodalFunction, a, b, Nmax)
	xMin_dic = Dichotomy(UnimodalFunction, a, b, eps)

print('Найденный методом пасс. поиска минимум:', xMin_ps)
print('Найденный методом дихотомии минимум:', xMin_dic)


#!/usr/local/Cellar/python/2.7.6/bin/python
# -*- coding: utf-8 -*-

'''Standard python modules'''
import sys

'''For scientific computing'''
from numpy import *
import scipy

'''For plotting'''
from matplotlib import pyplot, cm
from mpl_toolkits.mplot3d import Axes3D

EX_DIRECTORY_PATH = '/Users/saburookita/Personal Projects/MachineLearningPy/'

def part1():
    A = eye(5)
    print A

def hypothesis(X, theta):
    """Hypothesis function for linear regression
    this should be inlined code, but it's here for clear representation purpsoes
    """
    return X.dot(theta)

def computeCostLoop(X, y, theta):
    """Compute cost but in the slower loop method, in order to show how the
    pre-vectorization algorithm initially works. Always start with loop version first
    and code the vectorized version later
    """
    m = len(y)  # or m = shape(y)[0], since y is 1D

    cumulative_sum = 0
    for i in range(0, m):
        cumulative_sum += (hypothesis(X[i], theta) - y[i]) ** 2
    cumulative_sum = (1.0 / (2 * m)) * cumulative_sum
    return cumulative_sum

def computeCost(X, y, theta):
    """Compute cost, vectorized version"""
    m 	 = len(y)
    term = hypothesis(X, theta) - y
    # sum( term**2 ) in this case ~= term.T.dot( term )
    return (term.T.dot(term) / (2 * m))[0, 0]

def gradientDescentLoop(X, y, theta, alpha, iterations):
    """Gradient descent in loop version"""
    grad = copy(theta)
    m 	 = len(y)
    n 	 = shape(X)[1]

    for counter in range(0, iterations):
        # create n number of cumulative inner sums
        cum_sum = [0 for x in range(0, n)]

        for j in range(0, n):
            for i in range(0, m):
                term 		= (hypothesis(X[i], grad) - y[i])
                cum_sum[j] += X[i, j] * (term)

        # assign new values for each gradient, this should be separate from the loop above
        # in order to achieve simulataneous update effect
        for j in range(0, n):
            grad[j] = grad[j] - cum_sum[j] * (alpha / m)

    return grad

def gradientDescent(X, y, theta, alpha, iterations):
    """Vectorized gradient descent"""
    grad = copy(theta)
    m 	 = len(y)

    for counter in range(0, iterations):
        inner_sum = X.T.dot(hypothesis(X, grad) - y)
        grad 	 -= alpha / m * inner_sum

    return grad

def plot(X, y):
    """Create a plot out of X and y data, X should exclude the intercept units.
    Call pyplot.show(block=True) in order to show the plot window"""
    pyplot.plot(X, y, 'rx', markersize=5 )
    pyplot.ylabel('Profit in $10,000s')
    pyplot.xlabel('Population of City in 10,000s')

def part2_1():
    data = genfromtxt( EX_DIRECTORY_PATH + "ex1data1.txt", delimiter=',')
    X, y = data[:, 0], data[:, 1]
    m 	 = len(y)
    y 	 = y.reshape(m, 1)

    plot(X, y)
    pyplot.show(block=True)

def part2_2():
    data = genfromtxt( EX_DIRECTORY_PATH + 'ex1data1.txt', delimiter=',')
    X, y = data[:, 0], data[:, 1]
    m 	 = len(y)
    y 	 = y.reshape(m, 1)

    X 			= c_[ones((m, 1)), X]
    theta 		= zeros((2, 1))
    iterations 	= 1500
    alpha 		= 0.01

    cost 	= computeCost(X, y, theta)  # should be 32.07
    theta 	= gradientDescent(X, y, theta, alpha, iterations)
    print cost
    print theta

    predict1 = array([1, 3.5]).dot(theta)
    predict2 = array([1, 7]).dot(theta)
    print predict1
    print predict2

    plot(X[:, 1], y)
    pyplot.plot(X[:, 1], X.dot(theta), 'b-')
    pyplot.show(block=True)

def part2_4():
    data = genfromtxt( EX_DIRECTORY_PATH + "/ex1data1.txt", delimiter=',')
    X, y = data[:, 0], data[:, 1]
    m 	 = len(y)
    y 	 = y.reshape(m, 1)
    X 	 = c_[ones((m, 1)), X]

    theta0_vals = linspace(-10, 10, 100)
    theta1_vals = linspace(-4, 4, 100)

    J_vals = zeros((len(theta0_vals), len(theta1_vals)), dtype=float64)
    for i, v0 in enumerate(theta0_vals):
        for j, v1 in enumerate(theta1_vals):
            theta 		 = array((theta0_vals[i], theta1_vals[j])).reshape(2, 1)
            J_vals[i, j] = computeCost(X, y, theta)

    R, P = meshgrid(theta0_vals, theta1_vals)

    fig = pyplot.figure()
    ax 	= fig.gca(projection='3d')
    ax.plot_surface(R, P, J_vals)
    pyplot.show(block=True)

    fig = pyplot.figure()
    # ax 	= fig.gca(projection='3d')
    pyplot.contourf(R, P, J_vals.T, logspace(-2, 3, 20))
    pyplot.plot(theta[0], theta[1], 'rx', markersize = 10)
    pyplot.show(block=True)

def main():
    set_printoptions(precision=6, linewidth=200)

    part1()
    part2_1()
    part2_2()
    part2_4()

    sys.exit()

if __name__ == '__main__':
    main()


import numpy as np
from math import sqrt

# Calculates the root-mean-square for the given data (data = 1-D array)
def calculateRMS(data):
    return sqrt(sum(n * n for n in data) / len(data))

# Calculates the pairwise ratios of the RMS values (one for each channel) for one window
# (every channel with all the others)
def pairwiseRatio(rmsDataOneWindow):
    ratios = []
    for i in range(len(rmsDataOneWindow)):
        for j in range(i, len(rmsDataOneWindow)):
            if i != j:
                ratios.append(rmsDataOneWindow[i] / rmsDataOneWindow[j])
    return ratios

# This method is called for calculating the feature vector for the given window data
# param: windowData = [[channel 1]...[channel n]] (data for one window)
# returns: featureVector = [rms_channel_1, ..., rms_channel_n, ratios]
def calculateFeatureForWindow(windowData, ratio=True):
    rmsData = []
    for channel in windowData:
        rmsData.append(calculateRMS(channel))

    featureVec = rmsData
    if ratio: featureVec = np.concatenate([featureVec, pairwiseRatio(rmsData)], axis=None)
    return featureVec


# IMPORTS
import numpy as np
import scipy.stats as stats
import matplotlib.pyplot as plt
import matplotlib.style as style
from IPython.core.display import HTML

# PLOTTING CONFIG
%matplotlib inline
style.use('fivethirtyeight')
plt.rcParams["figure.figsize"] = (14, 7)
HTML("""
<style>
.output_png {
    display: table-cell;
    text-align: center;
    vertical-align: center;
}
</style>
""")
plt.figure(dpi=100)

##### COMPUTATION #####
# DECLARING THE "TRUE" PARAMETERS UNDERLYING THE SAMPLE
mu_real = 10
sigma_real = 2

# DRAW A SAMPLE OF N=1000
np.random.seed(42)
sample = stats.norm.rvs(loc=mu_real, scale=sigma_real, size=1000)

# ESTIMATE MU AND SIGMA
mu_est = np.mean(sample)
sigma_est = np.std(sample)
print("Estimated MU: {}\nEstimated SIGMA: {}".format(mu_est, sigma_est))

##### PLOTTING #####
# SAMPLE DISTRIBUTION
plt.hist(sample, bins=50,normed=True, alpha=.25)

# TRUE CURVE
plt.plot(np.linspace(2, 18, 1000), norm.pdf(np.linspace(2, 18, 1000),loc=mu_real, scale=sigma_real))

# ESTIMATED CURVE
plt.plot(np.linspace(2, 18, 1000), norm.pdf(np.linspace(2, 18, 1000),loc=np.mean(sample), scale=np.std(sample)))

# LEGEND
plt.text(x=9.5, y=.1, s="sample", alpha=.75, weight="bold", color="#008fd5")
plt.text(x=7, y=.2, s="true distrubtion", rotation=55, alpha=.75, weight="bold", color="#fc4f30")
plt.text(x=5, y=.12, s="estimated distribution", rotation=55, alpha=.75, weight="bold", color="#e5ae38")

# TICKS
plt.tick_params(axis = 'both', which = 'major', labelsize = 18)
plt.axhline(y = 0, color = 'black', linewidth = 1.3, alpha = .7)

# TITLE, SUBTITLE & FOOTER
plt.text(x = 0, y = 0.3, s = "Normal Distribution",
               fontsize = 26, weight = 'bold', alpha = .75)
plt.text(x = 0, y = 0.265,
         s = 'Depicted below is the distribution of a sample (blue) drawn from a normal distribution with $\mu = 10$\nand $\sigma = 2$ (red). Also the estimated distrubution with $\mu \sim {:.3f} $ and $\sigma \sim {:.3f} $ is shown (yellow).'.format(np.mean(sample), np.std(sample)),
         fontsize = 19, alpha = .85)
plt.text(x = 0,y = -0.025,
         s = '   ©Joshua Görner                                                                                github.com/jgoerner   ',
         fontsize = 14, color = '#f0f0f0', backgroundcolor = 'grey');


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

# In[1]:

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import scipy.stats as st
import statsmodels.api as sm
import pingouin as pg
get_ipython().run_line_magic('matplotlib', 'inline')

# In[2]:

df = pd.read_csv("/home/atrides/Desktop/R/statistics_with_Python/10_GLM1_ANOVA/Data_Files/Viagra.dat", sep='\t')
df.head()

# In[3]:

# lineplot
_ = sns.lineplot(x='dose', y='libido', data = df, err_style='bars')

# In[4]:

# describing statistical properties
df.groupby('dose')['libido'].describe()

# In[5]:

# ideal value for normal distribution , skewness = 0 , excess of kurtosis(here, same as kurtosis) = 0
for i in sorted(df['dose'].unique()):
    print(f'For group with dose = {i} : ')
    print(st.describe(df[df['dose']==i].libido), '\n')

# In[6]:

df_dose1 = df[df['dose']==1]
df_dose2 = df[df['dose']==2]
df_dose3 = df[df['dose']==3]

# In[7]:

# Levene's Test for Homogeneity of Variance (center = median(by default))
stat, p = st.levene(df_dose1['libido'], df_dose2['libido'], df_dose3['libido'])
print(stat, p)

# #### # since the above p value is not significant , i.e, p>.05, we can say that homogeneity of variance is maintained. just for the sake of visualization I am going to draw the boxplot .

# In[8]:

_ = sns.boxplot(x='dose', y='libido', data=df)

# In[9]:

from statsmodels.formula.api import ols
m01 = ols('libido~C(dose)', data=df).fit()
m01.summary()

# In[10]:

anova_table = sm.stats.anova_lm(m01)
anova_table

# #### # making some plots for assumption checking

# In[11]:

prediction = pd.DataFrame(m01.fittedvalues)
prediction.columns = ['predicted']
prediction['standarized_prediction']  = (prediction['predicted']-prediction['predicted'].mean())/prediction['predicted'].std()
prediction.head()

# In[12]:

import statsmodels.stats.outliers_influence as sms
summary_frame = sms.OLSInfluence(m01).summary_frame()
summary_frame = pd.merge(summary_frame, prediction, how = 'inner', left_index = True, right_index = True)
summary_frame .head()

# In[13]:

_ = sns.scatterplot(y = 'standard_resid', x='standarized_prediction', data = summary_frame)
_ = plt.axhline(y=0)

# #### # This graph can be used for testing homogeneity of variance. We encountered this kind of plot previously; essentially, if it has a funnel shape then we’re in trouble. The plot we have shows points that are equally spread for the three groups, which implies that variances are similar across groups (which was also the conclusion reached by Levene’s test).

# In[14]:

_ = pg.qqplot(summary_frame['standard_resid'], confidence=False)

# #### # The second plot is a Q-Q plot , which tells us something about the normality of residuals in the model. We want our residuals to be normally distributed, which means that the dots on the graph should cling  to the diagonal line. Ours look like they have had a bit of an argument with the diagonal line, which suggests that we may not be able to assume normality of errors and should perhaps use a robust version of ANOVA instead.

# In[15]:

# Doing Welch anova in the case if homogeniety of variance  is violated(our data here dont need this test)
aov = pg.welch_anova(dv='libido', between='dose', data=df)
aov

# ## Robust ANOVA (for independent samples)

# In[16]:

st.kruskal(df_dose1['libido'], df_dose2['libido'], df_dose3['libido'])

# # Planned Comparison

# #### https://www.statsmodels.org/devel/examples/notebooks/generated/contrasts.html#examples-notebooks-generated-contrasts--page-root

# In[17]:

contrast1 = [-2,1,1]
contrast2 = [0,-1,1]

# In[18]:

contrast = np.vstack((contrast1, contrast2))
contrast = contrast.T
print(contrast)

# In[19]:

m02 = ols("libido ~ C(dose, contrast)", data=df).fit()
m02.summary()

# # Trend Analysis

# In[20]:

from patsy.contrasts import Poly
levels = df.dose.unique().tolist()
contrast = Poly().code_without_intercept(levels)
print(contrast.matrix)

# In[21]:

m03 = ols("libido ~ C(dose, Poly)", data=df).fit()
m03.summary()

# # Post-hoc tests

# In[22]:

from statsmodels.sandbox.stats.multicomp import MultiComparison

# In[23]:

multicomp = MultiComparison(df['libido'], df['dose'])   # testfunc

# #### # https://pythonhealthcare.org/2018/04/13/55-statistics-multi-comparison-with-tukeys-test-and-the-holm-bonferroni-method/
# #### # https://www.statsmodels.org/stable/generated/statsmodels.sandbox.stats.multicomp.MultiComparison.html

# In[24]:

# Bonferroni
com = multicomp.allpairtest(st.ttest_ind, method='bonf')
print(com[0])

# In[25]:

# Holm
comp = multicomp.allpairtest(st.ttest_ind, method='Holm')
print (comp[0])

# In[26]:

# Tukey HSD
co = multicomp.tukeyhsd(alpha=0.05)
co.summary()

# ### another library for post-hoc tests
# #### # https://scikit-posthocs.readthedocs.io/en/latest/posthocs_api/

# In[27]:

import scikit_posthocs as sk_ph

# In[28]:

sk_ph.posthoc_tukey_hsd(df['libido'], df['dose'])

# In[29]:

sk_ph.posthoc_tukey(df, val_col = 'libido', group_col='dose')

# ## Robust Post-hoc test

# In[30]:

sk_ph.posthoc_wilcoxon(df, val_col = 'libido', group_col='dose')

# #### # from above table it seems that groups (1,3) and (2,3) are significant.
#

# In[31]:

anova_table

# In[32]:

SSt = 43.73
SSm = 20.133
SSr = 23.6

# In[33]:

R_squared = SSm/SSt    # here R_squared is called eta_squared
R_squared

# In[34]:

effect_size = np.sqrt(R_squared)   #R
effect_size

# #### # a more complex measure is omega-squared , which adjust for the fact that we are doing finding effect size for population and not just our sample

# In[35]:

MSr = 1.967
df_m = 2
ω2 = (SSm-df_m*MSr)/(SSt+MSr)
ω2

# In[36]:

ω = np.sqrt(ω2)       # ω, more accurate measure of R, treat ω as unbiased R
ω

# In[37]:

# effect sizes for different pair of groups
def s_within(s1,s2,n1,n2):
    return np.sqrt((((n1-1)*(s1**2))+((n2-1)*(s2**2)))/(n1+n2-2))

def mes(m1, m2, s1, s2, n1, n2):
    s_ = s_within(s1,s2,n1,n2)
    d = (m1-m2)/s_   # cohen's D
    d_var = (n1+n2)/(n1*n2) + (d**2)/(2*(n1+n2))    # variance of d

    df = n1+n2-2
    J = 1-3/(4*df-1)   # correction factor

    g = J*d          #   Hedges' g

    g_var = (J**2)*d_var     #   variance of g

    a = (n1+n2)**2/(n1*n2)    # a corrects for inbalance in n1 & n2

    r = d/np.sqrt(d**2+a)  # correlation coeffecient

    r_var = (a**2 * d_var)/((d**2 + a)**3)     #   variance of r

    print(f'Cohen\'s d: {d}, var.d: {d_var}')
    print(f'Hedge\'s g: {g}, var.g: {g_var}')
    print(f'Correlation coeffecient: {r}, var.r: {r_var}')

# In[38]:

mes(2.2, 3.2, 1.3038405, 1.3038405, 5, 5)

# In[39]:

mes(2.2, 5, 1.3038405, 1.5811388, 5, 5)

# In[40]:

mes(3.2, 5, 1.3038405, 1.5811388, 5, 5)

# #### # The standard deviation of the effect size is of critical importance, since it indicates how much uncertainty is included in the measurement. A standard deviation that is too large will make the measurement nearly meaningless.

# # Effect sizes for the orthogonal contrasts

# In[41]:

def r_contrast(t,df):
    return np.sqrt(t**2/(t**2+df))

# In[42]:

dof = 15-2-1   # N-p-1 for normal regression , p is the number of predictors (in this case 2, the two con-trast variables
print(r_contrast(2.474, dof))

# In[43]:

dof = 15-2-1   # N-p-1 for normal regression , p is the number of predictors (in this case 2, the two con-trast variables
print(r_contrast(2.029, dof))

# #### # Both effects are fairly large.

# In[ ]:

import numpy,import scipy,import statsmodels,from statsmodels


# exec(open(".//09_Index_Arrays.py"))

import numpy as np
print "Executing: 09_Index_Arrays.py"

'''
NumPy Index Arrays
'''

# Change False to True for each block of code to see what it does

# Using index arrays
if False:
    a = np.array([1, 2, 3, 4])
    b = np.array([True, True, False, False])

    print a[b]
    print a[np.array([True, False, True, False])]

# Creating the index array using vectorized operations
if False:
    a = np.array([1, 2, 3, 2, 1])
    b = (a >= 2)

    print a[b]
    print a[a >= 2]

# Creating the index array using vectorized operations on another array
if False:
    a = np.array([1, 2, 3, 4, 5])
    b = np.array([1, 2, 3, 2, 1])

    print b == 2
    print a[b == 2]

# Time spent in the classroom in the first week for 20 students
time_spent = np.array([
       12.89697233,    0.        ,   64.55043217,    0.        ,
       24.2315615 ,   39.991625  ,    0.        ,    0.        ,
      147.20683783,    0.        ,    0.        ,    0.        ,
       45.18261617,  157.60454283,  133.2434615 ,   52.85000767,
        0.        ,   54.9204785 ,   26.78142417,    0.
])

# Days to cancel for 20 students
days_to_cancel = np.array([
      4,   5,  37,   3,  12,   4,  35,  38,   5,  37,   3,   3,  68,
     38,  98,   2, 249,   2, 127,  35
])

# This is my version which is also correct

# def mean_time_for_paid_students(time_spent, days_to_cancel):
#    '''
#    Fill in this function to calculate the mean time spent in the classroom
#    for students who stayed enrolled at least (greater than or equal to) 7 days.
#    Unlike in Lesson 1, you can assume that days_to_cancel will contain only
#    integers (there are no students who have not canceled yet).

#    The arguments are NumPy arrays. time_spent contains the amount of time spent
#    in the classroom for each student, and days_to_cancel contains the number
#    of days until each student cancel. The data is given in the same order
#    in both arrays.
#    '''
#    return time_spent[days_to_cancel >= 7].mean()

# This is udacity's version

def mean_time_for_paid_students(time_spent, days_to_cancel):
    # create the index array
    is_paid = days_to_cancel >= 7
    paid_time = time_spent[is_paid]
    return paid_time.mean()


import pandas as pd
import math
import numpy as np
from sklearn.linear_model import  LinearRegression as lr

from sklearn.linear_model.base import LinearModel
from sklearn.base import RegressorMixin
from sklearn.utils import check_X_y
import numpy as np

class ConstrainedLinearRegression(LinearModel, RegressorMixin):

    def __init__(self, fit_intercept=True, normalize=False, copy_X=True, nonnegative=False, tol=1e-15):
        self.fit_intercept = fit_intercept
        self.normalize = normalize
        self.copy_X = copy_X
        self.nonnegative = nonnegative
        self.tol = tol

    def fit(self, X, y, min_coef=None, max_coef=None):
        X, y = check_X_y(X, y, accept_sparse=['csr', 'csc', 'coo'], y_numeric=True, multi_output=False)
        X, y, X_offset, y_offset, X_scale = self._preprocess_data(
            X, y, fit_intercept=self.fit_intercept, normalize=self.normalize, copy=self.copy_X)
        self.min_coef_ = min_coef if min_coef is not None else np.repeat(-np.inf, X.shape[1])
        self.max_coef_ = max_coef if max_coef is not None else np.repeat(np.inf, X.shape[1])
        if self.nonnegative:
            self.min_coef_ = np.clip(self.min_coef_, 0, None)

        beta = np.zeros(X.shape[1]).astype(float)
        prev_beta = beta + 1
        hessian = np.dot(X.transpose(), X)
        while not (np.abs(prev_beta - beta)<self.tol).all():
            prev_beta = beta.copy()
            for i in range(len(beta)):
                grad = np.dot(np.dot(X,beta) - y, X)
                beta[i] = np.minimum(self.max_coef_[i],
                                     np.maximum(self.min_coef_[i],
                                                beta[i]-grad[i] / hessian[i,i]))

        self.coef_ = beta
        self._set_intercept(X_offset, y_offset, X_scale)
        return self

data = pd.read_csv("vivado_energy.csv")
data = np.array(data.values.tolist())
# print(data)
print(data[:, 0:4], data[:, 4])
# print(data.head())
# median=math.floor(data.DSP.median())
# print(median)

model = ConstrainedLinearRegression(nonnegative=True)
model.fit(data[:, 0:4], data[:, 4])
print(model.intercept_)
print(model.coef_)
# print(data.head())
# print(model.predict([[1,1,1,1]]))
# reg = lr()
# reg.fit(data[['BRAM','DSP','FF', 'LUT']], data.Power)

# m= reg.coef_
# b= reg.intercept_

# # print(reg.predict([[1308,    118, 106679,  208976]]))
df = pd.read_csv('vgg.csv')
# print(df.head())
pred_data = df.values.tolist()
pred = []
for i in range(len(pred_data)):
    print(pred_data[i])
    pred.append(model.predict([pred_data[i]]))
for i in range(len(pred)):
    print(np.round(pred[i], 3)[0])
#     print(pred[i])


# SET OF FUNCTIONS FOR SIGNAL PROCESSING

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

def FFT_periodic(t, x_t, one_sided = True, rms_scaling=False):
    dt = t[1]-t[0]
    T = t[-1] + dt
    fs = 1/dt
    N = x_t.shape[0]
    df = fs/N
    freq = np.arange(N)*df
    if one_sided: # One-sided spectrum
        Xf = 2*np.fft.fft(x_t)
        Xf[0] = Xf[0]/2
        if np.remainder(N,2)==0:
            Nsel = int(N/2)
        else:
            Nsel = int((N+1)/2)
        freq = freq[0:Nsel]
        Xf = Xf[0:Nsel]
    else: # Two-sided spectrum
        Xf = np.fft.fft(x_t)
    if rms_scaling:
        Xf[1:] = Xf[1:]/np.sqrt(2)
    return freq, Xf/(fs*T)

def FFT_transient(t, x_t, one_sided = True, rms_scaling=False):
    dt = t[1]-t[0]
    # T = t[-1] + dt
    fs = 1/dt
    N = x_t.shape[0]
    df = fs/N
    freq = np.arange(N)*df
    if one_sided: # One-sided spectrum
        Xf = 2*np.fft.fft(x_t)
        Xf[0] = Xf[0]/2
        if np.remainder(N,2)==0:
            Nsel = int(N/2)
        else:
            Nsel = int((N+1)/2)
        freq = freq[0:Nsel]
        Xf = Xf[0:Nsel]
    else: # Two-sided spectrum
        Xf = np.fft.fft(x_t)
    if rms_scaling:
        Xf[1:] = Xf[1:]/np.sqrt(2)
    return freq, Xf/fs

if __name__ == "__main__":

    # SIGNAL AQUISITION PARAMETERS
    Npoints = 2048
    fs = 2048
    df = fs/Npoints
    T = 1/df

    # BUILDING THE SIGNAL
    t = np.arange(0, T, T/Npoints)
    f1, f2, f3 = 10, 20, 40
    A1, A2, A3 = 1, 0.5, 0.25
    x_t = A1*np.sin(2*np.pi*t*f1) + A2*np.sin(2*np.pi*t*f2) + A3*np.sin(2*np.pi*t*f3)

    # PERFORM THE FAST FOURIER TRANSFORM OF SIGNAL
    one_sided = True
    freq, X_f = FFT_periodic(t, x_t, one_sided=one_sided)

    # CHECKING THE RMS OF SIGNAL IN BOTH DOMAINS - SEE THE PARSEVAL'S THEOREM
    x_t_RMS = np.sqrt(np.sum(x_t**2)/Npoints)
    if one_sided:
        _, Xf = FFT_periodic(t,x_t, one_sided=False)
    else:
        Xf = X_f.copy()
    Xf_RMS = np.sqrt(np.sum(np.abs(Xf)**2))
    print("The rms of signal in time domain is {}".format(x_t_RMS))
    print("The rms of signal in frequency domais is {}".format(Xf_RMS))

    # PLOT OF SIGNAL IN THE TIME DOMAIN
    fig = plt.figure(figsize=[12,7])
    ax = fig.add_subplot(1,1,1)
    first_plot = ax.plot(t, x_t, color=[1,0,0], linewidth=2, label="Flow out")
    ax.set_xlabel("Time [s]", fontsize = 14, fontweight = 'bold')
    ax.set_ylabel("Signal Amplitude [--]", fontsize = 14, fontweight = 'bold')
    plt.grid()
    plt.show()

    # PLOT OF SIGNAL IN THE FREQUENCY DOMAIN
    fig = plt.figure(figsize=[12,7])
    ax = fig.add_subplot(1,1,1)
    second_plot = ax.plot(freq, np.abs(X_f), color=[1,0,0], linewidth=2, label="Flow out")
    ax.set_xlabel("Frequency [Hz]", fontsize = 14, fontweight = 'bold')
    ax.set_ylabel("Signal Magnitude [--]", fontsize = 14, fontweight = 'bold')
    plt.grid()
    plt.show()