"""
demo_system_of_linear_equations.py

Solving a system of linear equations in a least squares way:
A @ X = B (matrix form)

Normal equation with Tikhonov regularization

Gradient algorithm with variable step size (steepest descent).
    Only for quadratic f(X) function. Requires gradient and hessian.
    Guaranteed to converge in n steps for n parameters, i.e. for X = [x1, ..., xn].T.
    X0 converges to X* for any X0.
    In parameter space, moving along orthogonal directions with variable step size.

Gradient algorithm with fixed step size.
    Only for quadratic f(X) function. Requires gradient and hessian.
    X0 converges to X* for any X0.
    In parameter space, moving along orthogonal directions with fixed step size.

Gradient algorithm with variable step size based on line search
    General algorithm for any f(X) function. Requires only gradient.
    X0 converges to X* for any X0.
    In parameter space, moving along orthogonal directions with variable step size determined by line search.
    Step size is determined by line search to ensure the descent property f(Xn+1) < f(Xn)

Conjugate gradient algorithm with variable step size (steepest descent).
    Only for quadratic f(X) function. Requires gradient and hessian.
    Guaranteed to converge in n steps for n parameters, i.e. for X = [x1, ..., xn].T.
    X0 converges to X* for any X0.
    In parameter space, moving along mutually conjugate directions with variable step size.

Conjugate gradient algorithm with variable step size based on line search
    General algorithm for any f(X) function. Requires only gradient.
    X0 converges to X* for any X0.
    In parameter space, moving along mutually conjugate directions with variable step size.
    Step size is determined by line search to ensure the descent property f(Xn+1) < f(Xn).
    For non-quadratic f(X) conjugate direction vectors will tend to deteriorate and need to be
        reset to the negative gradient every n steps.

Newton's algorithm.
    General algorithm for any f(X) function. Requires gradient and hessian.
    Can converge in as few as 1 step (for quadratic f(X)).
    X0 should be close to X*.
    In parameter space, moving to the MIN of parabola in 1 step.

Newton's algorithm with variable step size based on line search
    General algorithm for any f(X) function. Requires gradient and hessian.
    Can converge in as few as 1 step (for quadratic f(X)).
    X0 converges to X* for any X0.
    In parameter space, moving to the MIN of parabola in 1 step. Might be affected by linesearch.
    Step size is determined by line search to ensure the descent property f(Xn+1) < f(Xn).

Quasi-Newton algorithm with variable step size (steepest descent).
    Only for quadratic f(X) function. Requires gradient and hessian.
    Guaranteed to converge in n steps for n parameters, i.e. for X = [x1, ..., xn].T.
    X0 converges to X* for any X0.
    In parameter space, moving along mutually conjugate directions with variable step size.
    For non-quadratic f(X) conjugate direction vectors will tend to deteriorate and need to be
        reset to the negative gradient every n steps.

Quasi-Newton algorithm with variable step size based on line search
    General algorithm for any f(X) function. Requires only gradient.
    X0 converges to X* for any X0.
    In parameter space, moving along mutually conjugate directions with variable step size.
    Step size is determined by line search to ensure the descent property f(Xn+1) < f(Xn).
    For non-quadratic f(X) conjugate direction vectors will tend to deteriorate and need to be
        reset to the negative gradient every n steps.

Naive random walk algorithm
    General algorithm for any f(X) function. Requires only residuals.
    X0 converges to X* for any X0.

Simulated annealing algorithm
    General algorithm for any f(X) function. Requires only residuals.
    X0 converges to X* for any X0.

Particle swarm optimization algorithm
    General algorithm for any f(X) function. Requires only residuals.
    X0 converges to X* for any X0.
"""

import numpy as np
from gradient_algorithm import gradient_algorithm_var_alpha, gradient_algorithm_fixed_alpha, gradient_algorithm_linesearch
from conjugate_gradient_algorithm import conjugate_gradient_algorithm, conjugate_gradient_algorithm_linesearch
from newton_algorithm import newton_algorithm, newton_algorithm_linesearch
from quasi_newton_algorithm import quasi_newton_algorithm, quasi_newton_algorithm_linesearch
from naive_random_search_algorithm import naive_random_search_algorithm
from simulated_annealing_algorithm import simulated_annealing_algorithm
from particle_swam_optimization_algorithm import particle_swam_optimization_algorithm
from print_report import print_report
import time

reg_coef = 0.01; # regularization coefficient
# A @ X = B (matrix form)
# Minimize the following objective function:
# X = [x0, ..., xn].T;
# f(X) = norm(A * X - B)^2 =  X.T * (A.T @ A) @ X - 2 * (A.T @ B).T @ X + B.T @ B + reg_coef * X.T @ X;
N_eqs = 5000; # number of equations
N_pars = 10; # number of parameters.
A = np.random.rand(N_eqs, N_pars);
B = np.random.rand(N_eqs, 1);
Q = A.T @ A;
func = lambda X : X.T @ Q @ X - 2 * (A.T @ B).T @ X + B.T @ B + reg_coef * X.T @ X;
func_grad = lambda X : 2 * Q @ X - 2 * (A.T @ B) + 2 * reg_coef * X;
func_hessian = lambda X : 2 * Q + 2 * reg_coef;
# Objective function for naive random walk and simulated annealing algorithms
func_error = lambda X : np.linalg.norm((A @ X - B + reg_coef * X.T @ X), axis = 0) ** 2;
# Objective function for particle swarm optimization algorithm (particles along row dimension, axis = 0)
def func_error_ps(X):
    a = np.linalg.norm((X @ A.T - B.T + reg_coef * np.diag(X @ X.T).reshape(-1, 1)), axis = 1) ** 2;
    return a.reshape(-1,1);

# Normal equation with Tikhonov regularization
print('***********************************************************************');
print('Normal equation with Tikhonov regularization');
start = time.time();
X = np.linalg.solve(Q + reg_coef * np.eye(N_pars), A.T @ B);
end = time.time();
print("Norm(X): %0.3f" % (np.linalg.norm(X)));
print('f(X): %0.3f' % (func(X)));
print('Elapsed time [s]: %0.5f' % (end - start));
print('***********************************************************************\n');

# Gradient algorithm with variable step size (steepest descent)
print('***********************************************************************');
print('Gradient algorithm with variable step size (steepest descent)');
N_iter_max = 1000;
tolerance_x = 10e-6;
tolerance_y = 10e-8;
options = {'tolerance_x' : tolerance_x, 'tolerance_y' : tolerance_y, 'N_iter_max' : N_iter_max};
X0 = np.zeros((N_pars, 1));
start = time.time();
X, report = gradient_algorithm_var_alpha(X0, func, func_grad, func_hessian, options);
end = time.time();
print_report(func, report);
print('Elapsed time [s]: %0.5f' % (end - start));
print('***********************************************************************\n');

# Gradient algorithm with fixed step size
print('***********************************************************************');
print('Gradient algorithm with fixed step size');
N_iter_max = 1000;
tolerance_x = 10e-6;
tolerance_y = 10e-8;
options = {'tolerance_x' : tolerance_x, 'tolerance_y' : tolerance_y, 'N_iter_max' : N_iter_max};
X0 = np.zeros((N_pars, 1));
start = time.time();
X, report = gradient_algorithm_fixed_alpha(X0, func, func_grad, func_hessian, options);
end = time.time();
print_report(func, report);
print('Elapsed time [s]: %0.5f' % (end - start));
print('***********************************************************************\n');

# Gradient algorithm with variable step size based on line search
print('***********************************************************************');
print('Gradient algorithm with variable step size based on line search');
N_iter_max = 1000;
tolerance_x = 10e-6;
tolerance_y = 10e-8;
options = {'tolerance_x' : tolerance_x, 'tolerance_y' : tolerance_y, 'N_iter_max' : N_iter_max};
X0 = np.zeros((N_pars, 1));
start = time.time();
X, report = gradient_algorithm_linesearch(X0, func, func_grad, options);
end = time.time();
print_report(func, report);
print('Elapsed time [s]: %0.5f' % (end - start));
print('***********************************************************************\n');

# Conjugate gradient algorithm with variable step size (steepest descent)
print('***********************************************************************');
print('Conjugate gradient algorithm with variable step size (steepest descent)');
N_iter_max = 1000;
tolerance_x = 10e-6;
tolerance_y = 10e-8;
options = {'tolerance_x' : tolerance_x, 'tolerance_y' : tolerance_y, 'N_iter_max' : N_iter_max};
X0 = np.zeros((N_pars, 1));
start = time.time();
X, report = conjugate_gradient_algorithm(X0, func, func_grad, func_hessian, options);
end = time.time();
print_report(func, report);
print('Elapsed time [s]: %0.5f' % (end - start));
print('***********************************************************************\n');

# Conjugate gradient algorithm with variable step size based on line search
print('***********************************************************************');
print('Conjugate gradient algorithm with variable step size based on line search');
N_iter_max = 1000;
tolerance_x = 10e-6;
tolerance_y = 10e-8;
options = {'tolerance_x' : tolerance_x, 'tolerance_y' : tolerance_y, 'N_iter_max' : N_iter_max};
X0 = np.zeros((N_pars, 1));
start = time.time();
X, report = conjugate_gradient_algorithm_linesearch(X0, func, func_grad, options);
end = time.time();
print_report(func, report);
print('Elapsed time [s]: %0.5f' % (end - start));
print('***********************************************************************\n');

# Newton's algorithm
print('***********************************************************************');
print('Newton\'s algorithm');
N_iter_max = 1000;
tolerance_x = 10e-6;
tolerance_y = 10e-8;
options = {'tolerance_x' : tolerance_x, 'tolerance_y' : tolerance_y, 'N_iter_max' : N_iter_max};
X0 = np.zeros((N_pars, 1));
start = time.time();
X, report = newton_algorithm(X0, func, func_grad, func_hessian, options);
end = time.time();
print_report(func, report);
print('Elapsed time [s]: %0.5f' % (end - start));
print('***********************************************************************\n');

# Newton's algorithm with variable step size based on line search
print('***********************************************************************');
print('Newton\'s algorithm with variable step size based on line search');
N_iter_max = 1000;
tolerance_x = 10e-6;
tolerance_y = 10e-8;
options = {'tolerance_x' : tolerance_x, 'tolerance_y' : tolerance_y, 'N_iter_max' : N_iter_max};
X0 = np.zeros((N_pars, 1));
start = time.time();
X, report = newton_algorithm_linesearch(X0, func, func_grad, func_hessian, options);
end = time.time();
print_report(func, report);
print('Elapsed time [s]: %0.5f' % (end - start));
print('***********************************************************************\n');

# Quasi-Newton algorithm with variable step size (steepest descent)
print('***********************************************************************');
print('Quasi-Newton algorithm with variable step size (steepest descent)');
N_iter_max = 1000;
tolerance_x = 10e-6;
tolerance_y = 10e-8;
options = {'tolerance_x' : tolerance_x, 'tolerance_y' : tolerance_y, 'N_iter_max' : N_iter_max};
X0 = np.zeros((N_pars, 1));
start = time.time();
X, report = quasi_newton_algorithm(X0, func, func_grad, func_hessian, options);
end = time.time();
print_report(func, report);
print('Elapsed time [s]: %0.5f' % (end - start));
print('***********************************************************************\n');

# Quasi-Newton algorithm with variable step size based on line search
print('***********************************************************************');
print('Quasi-Newton algorithm with variable step size based on line search');
N_iter_max = 1000;
tolerance_x = 10e-6;
tolerance_y = 10e-8;
options = {'tolerance_x' : tolerance_x, 'tolerance_y' : tolerance_y, 'N_iter_max' : N_iter_max};
X0 = np.zeros((N_pars, 1));
start = time.time();
X, report = quasi_newton_algorithm_linesearch(X0, func, func_grad, options);
end = time.time();
print_report(func, report);
print('Elapsed time [s]: %0.5f' % (end - start));
print('***********************************************************************\n');

# Naive random walk algorithm
print('***********************************************************************');
print('Naive random walk algorithm');
N_iter_max = 10000;
tolerance_x = 10e-6;
tolerance_y = 10e-8;
X_lower = -1 * np.ones((N_pars, 1)); # X lower bound
X_upper = 1 * np.ones((N_pars, 1)); # X upper bound
alpha = 1.0; # step size
options = {'tolerance_x' : tolerance_x, 'tolerance_y' : tolerance_y, 'N_iter_max' : N_iter_max, 'x_lower' : X_lower,
           'x_upper' : X_upper, 'alpha' : alpha};
X0 = X_lower + (X_upper - X_lower) * np.random.rand(X_lower.size, 1);
start = time.time();
X, report = naive_random_search_algorithm(X0, func_error, options);
end = time.time();
print_report(func, report);
print('Elapsed time [s]: %0.5f' % (end - start));
print('***********************************************************************\n');

# Simulated annealing algorithm
print('***********************************************************************');
print('Simulated annealing algorithm');
N_iter_max = 10000;
tolerance_x = 10e-6;
tolerance_y = 10e-8;
X_lower = -1 * np.ones((N_pars, 1)); # X lower bound
X_upper = 1 * np.ones((N_pars, 1)); # X upper bound
alpha = 1; # step size
gamma = 1.0; # controls temperature decay, gamma > 0
options = {'tolerance_x' : tolerance_x, 'tolerance_y' : tolerance_y, 'N_iter_max' : N_iter_max, 'x_lower' : X_lower,
           'x_upper' : X_upper, 'alpha' : alpha, 'gamma' : gamma};
X0 = X_lower + (X_upper - X_lower) * np.random.rand(X_lower.size, 1);
start = time.time();
X, report = simulated_annealing_algorithm(X0, func_error, options);
end = time.time();
print_report(func, report);
print('Elapsed time [s]: %0.5f' % (end - start));
print('***********************************************************************\n');

# Particle swarm optimization algorithm
print('***********************************************************************');
print('Particle swarm optimization algorithm');
N_iter_max = 1000;
tolerance_x = 10e-8;
tolerance_y = 10e-8;
X_lower = -1 * np.ones((N_pars, 1)); # X lower bound
X_upper = 1 * np.ones((N_pars, 1)); # X upper bound
d_lower = -0.25; # direction (aka velocity) lower bound
d_upper = 0.25; # direction (aka velocity) upper bound
N_ps = 1000; # number of particles
w = 1.0; # inertial constant, w < 1
c1 = 1.0; # cognitive/independent component, c1 ~ 2
c2 = 0; # social component, c2 ~ 2
alpha = 1.0; # step size
options = {'tolerance_x' : tolerance_x, 'tolerance_y' : tolerance_y, 'N_iter_max' : N_iter_max, 'x_lower' : X_lower,
           'x_upper' : X_upper, 'alpha' : alpha, 'd_lower' : d_lower, 'd_upper' : d_upper, 'N_ps' : N_ps, 'w' : w, 'c1' : c1, 'c2' : c2};
start = time.time();
X, report = particle_swam_optimization_algorithm(func_error_ps, options);
end = time.time();
print_report(func, report);
print('Elapsed time [s]: %0.5f' % (end - start));
print('***********************************************************************\n');


import psyneulink.components.functions.function as Function
import psyneulink.globals.keywords as kw
import numpy as np
import pytest

SIZE=4
# Some metrics (CROSS_ENTROPY) don't like 0s
test_var = [np.random.rand(SIZE) + Function.EPSILON, np.random.rand(SIZE) + Function.EPSILON]
v1 = test_var[0]
v2 = test_var[1]
norm = len(test_var[0])

test_data = [
    (test_var, kw.DIFFERENCE, False, None, np.sum(np.abs(v1 - v2))),
    (test_var, kw.DIFFERENCE, True,  None, np.sum(np.abs(v1 - v2))/norm),
    (test_var, kw.EUCLIDEAN, False, None, np.linalg.norm(v1 - v2)),
    (test_var, kw.EUCLIDEAN, True,  None, np.linalg.norm(v1 - v2)/norm),
    (test_var, kw.ANGLE, False, "Needs sci-py", 0),
    (test_var, kw.ANGLE, True,  "Needs sci-py", 0/norm),
    (test_var, kw.CORRELATION, False, None, np.correlate(v1, v2)),
    (test_var, kw.CORRELATION, True,  None, np.correlate(v1, v2)/norm),
    (test_var, kw.PEARSON, False, None, np.corrcoef(v1, v2)),
    (test_var, kw.PEARSON, True,  None, np.corrcoef(v1, v2)/norm),
    (test_var, kw.CROSS_ENTROPY, False, None, -np.sum(v1*np.log(v2))),
    (test_var, kw.CROSS_ENTROPY, True,  None, -np.sum(v1*np.log(v2))/norm),
]

# use list, naming function produces ugly names
names = [
    "DIFFERENCE",
    "DIFFERENCE NORMALIZED",
    "EUCLIDEAN",
    "EUCLIDEAN NORMALIZED",
    "ANGLE",
    "ANGLE NORMALIZED",
    "CORRELATION",
    "CORRELATION NORMALIZED",
    "PEARSON",
    "PEARSON NORMALIZED",
    "CROSS_ENTROPY",
    "CROSS_ENTROPY NORMALIZED",
]

@pytest.mark.function
@pytest.mark.distance_function
@pytest.mark.parametrize("variable, metric, normalize, fail, expected", test_data, ids=names)
@pytest.mark.benchmark
def test_basic(variable, metric, normalize, fail, expected, benchmark):
    if fail is not None:
        # This is a rather ugly hack to stop pytest benchmark complains
        benchmark.disabled = True
        benchmark(lambda _:0,0)
        pytest.xfail(fail)
        return
    f = Function.Distance(default_variable=variable, metric=metric, normalize=normalize)
    benchmark.group = "DistanceFunction " + metric + ("-normalized" if normalize else "")
    res = benchmark(f.function, variable)
    assert np.allclose(res, expected)


"""Utility functions for computing useful statistics."""

import numpy as np

def pearson_residuals(y, pred):
    """Compute Pearson residuals.

    Reference:
    https://web.as.uky.edu/statistics/users/pbreheny/760/S11/notes/4-12.pdf

    :param array-like[int] y: target labels. 1 is positive label, 0 is negative
        label
    :param array-like[float] pred: predicted labels.
    :returns: pearson residual.
    :rtype: array-like[float]
    """
    y, pred = np.array(y), np.array(pred)
    return (y - pred) / np.sqrt(pred * (1 - pred))

def deviance_residuals(y, pred):
    """Compute Deviance residuals.

    Reference:
    https://web.as.uky.edu/statistics/users/pbreheny/760/S11/notes/4-12.pdf

    Formula:
    d = sign * sqrt(-2 * {y * log(p) + (1 - y) * log(1 - p)})
    - where sign is -1 if y = 1 and 1 if y = 0
    - y is the true label
    - p is the predicted probability

    :param array-like[int] y: target labels. 1 is positive label, 0 is negative
        label
    :param array-like[float] pred: predicted labels.
    :returns: deviance residual.
    :rtype: array-like[float]
    """
    y, pred = np.array(y), np.array(pred)
    sign = np.array([1 if y_i else -1 for y_i in y])
    return sign * np.sqrt(-2 * (y * np.log(pred) + (1 - y) * np.log(1 - pred)))


import numpy as np

def SPSA_gradient(loss, currentParams, gradientCoefficient):
    r'''Computes an estimator of the gradient using the procedure
    described in the SPSA algorithm.

    Inputs:
        loss: The loss function
        currentParams: The current value for the parameters
        gradientCoefficient: The coefficient c_n, which controls how much the current parameters are
                                               perturbed when computing the gradient

    Returns:
        gradient: The SPSA-based gradient of the loss function at currentParams'''

    numParams = len(currentParams)
    # Generate a random perturbation using the Rademacher distribution
    randomPerturbation = 2*np.random.binomial(1, .5, size=numParams) - 1

    gradient = (loss(currentParams + gradientCoefficient*randomPerturbation) - loss(currentParams - gradientCoefficient*randomPerturbation))\
                        /(gradientCoefficient*randomPerturbation)

    return gradient

def SPSA_update(loss, currentParams, updateCoefficient, gradientCoefficient):
    r'''Performs a parameter update according to the SPSA approach.

    NOTE: This function isn't aware of the notion of iterations, or anything of that sort.

    Inputs:
        loss: The loss function
        currentParams: The current value for the parameters
        updateCoefficient: The coefficient a_n, which controls how the current parameters are updated
                                            when including the gradient
        gradientCoefficient: The coefficient c_n, which controls how much the current parameters are
                                               perturbed when computing the gradient

    Returns:
        The updated parameter values'''

    grad = SPSA_gradient(loss, currentParams, gradientCoefficient)

    return currentParams - updateCoefficient*grad


try:
    from numpy import euler_gamma as _euler_gamma
    import numpy as _np
    from math import sqrt as _sqrt, log as _log, pi as _pi, exp as _exp
    from typing import Union, Dict, List
    from univariate._base import SemiInfinite
except Exception as e:
    print(f"some modules are missing {e}")

class Gumbell(SemiInfinite):
    """
    This class contains methods concerning Gumbel Distirbution [#]_.

    .. math::
        \\text{Gumbel}(x;\\mu,\\beta) = \\frac{1}{\\beta} \\exp{-\\Big( \\frac{x-\\mu}{\\beta} + \\exp{ \\frac{x-\\mu}{\\beta}} \\Big)}

    Args:

        location(float): location parameter (:math:`\\mu`)
        scale(float): scale parameter (:math:`\\beta`) where scale > 0
        x(float): random variable

    Reference:
        .. [#] Wikipedia contributors. (2020, November 26). Gumbel distribution. https://en.wikipedia.org/w/index.php?title=Gumbel_distribution&oldid=990718796
    """

    def __init__(self, location: float, scale: float):
        if scale < 0:
            raise ValueError(
                f'scale parameter should be greater than 0. The value of the scale parameter is: {scale}')

        self.location = location
        self.scale = scale

    def pdf(self, x: Union[List[float], _np.ndarray, float]) -> Union[float, _np.ndarray]:
        """

        Args:
            x (Union[List[float], numpy.ndarray, float]): random variable(s)

        Returns:
            Union[float, numpy.ndarray]: evaluation of pdf at x
        """
        mu = self.location
        beta = self.scale

        if isinstance(x, (_np.ndarray, List)):
            if not type(x) is _np.ndarray:
                x = _np.array(x)
            z = (x-mu)/beta
            return (1/beta)*_np.exp(-(z+_np.exp(-z)))

        z = (x-mu)/beta
        return (1/beta)*_exp(-(z+_exp(-z)))

    def cdf(self, x: Union[List[float], _np.ndarray, float]) -> Union[float, _np.ndarray]:
        """
        Args:
            x (Union[List[float], numpy.ndarray, float]): data point(s) of interest

        Returns:
            Union[float, numpy.ndarray]: evaluation of cdf at x
        """
        mu = self.location
        beta = self.scale

        if isinstance(x, (_np.ndarray, List)):
            if not type(x) is _np.ndarray:
                x = _np.array(x)
            return _np.exp(-_np.exp(-(x-mu)/beta))
        return _exp(-_exp(-(x - mu)/beta))

    def mean(self) -> float:
        """
        Returns: Mean of the Gumbel distribution.
        """
        return self.location+(self.scale*_euler_gamma)

    def median(self) -> float:
        """
        Returns: Median of the Gumbel distribution.
        """
        return self.location - (self.scale*_log(_log(2)))

    def mode(self) -> float:
        """
        Returns: Mode of the Gumbel distribution.
        """
        return self.location

    def var(self) -> float:
        """
        Returns: Variance of the Gumbel distribution.
        """
        return pow(_pi, 2/6)*pow(self.scale, 2)

    def std(self) -> float:
        """
        Returns: Standard deviation of the Gumbel distribution.
        """
        return _sqrt(self.var())

    def skewness(self) -> float:
        """
        Returns: Skewness of the Gumbel distribution.
        """
        return 1.14

    def kurtosis(self) -> float:
        """
        Returns: Kurtosis of the Gumbel distribution.
        """
        return 2.4

    def summary(self) -> Dict[str, float]:
        """
        Returns:
            Dictionary of Gumbel distirbution moments. This includes standard deviation.
        """
        return {
            'mean': self.mean(), 'median': self.median(), 'mode': self.mode(),
            'var': self.var(), 'std': self.std(), 'skewness': self.skewness(), 'kurtosis': self.kurtosis()
        }


import csv
import math
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import time
import numpy as np
from scipy.special import expit
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter

# read the data
csvfile=open("q4x.dat", 'r')
x = list(csv.reader(csvfile,delimiter=' '))
csvfile=open("q4y.dat", 'r')
y = list(csv.reader(csvfile))

# Alaska is 0 and Canada is 1
m=len(x)
n=2
x3=[]
y2=[]
for i in range(m):
    x3.append([float(x[i][0]),float(x[i][1])])
    if(y[i][0]=="Alaska"):
        y2.append(0)
    else:
        y2.append(1)

# normalise the data
meanx=[0,0]
# meanx[0]=sum(x3[0])/m
# meanx[1]=sum(x3[1])/m

for i in range(m):
    meanx[0]+=x3[i][0]
    meanx[1]+=x3[i][1]
meanx[0]/=m
meanx[1]/=m

v=[0,0]
for i in range(m):
    temp=x3[i][0]-meanx[0]
    v[0]+=temp*temp
    temp=x3[i][1]-meanx[1]
    v[1]+=temp*temp
v[0]/=m
v[0]=math.sqrt(v[0])
v[1]/=m
v[1]=math.sqrt(v[1])
for i in range(m):
    x3[i][0]=(x3[i][0]-meanx[0])/v[0]
    x3[i][1]=(x3[i][1]-meanx[1])/v[1]

x2=[]
# for y=1
x4=[]
x5=[]
# for y=0
x6=[]
x7=[]

# calculate u0 & u1
u0=np.array([0.,0.])
u1=np.array([0.,0.])
phi=0
m0=0
m1=0
for i in range(m):
    x2.append(np.array([x3[i][0],x3[i][1]]))

    if(y2[i]==1):
        x4.append(float(x3[i][0]))
        x5.append(float(x3[i][1]))
        u1+=x2[i]
        phi+=1
        m1+=1
    elif(y2[i]==0):
        x6.append(float(x3[i][0]))
        x7.append(float(x3[i][1]))
        u0+=x2[i]
        m0+=1
phi/=m
X=np.array(x2)
Y=np.array(y2)

u0/=m0
u1/=m1

print("u0=\n{}\n".format(u0))
print("u1=\n{}\n".format(u1))

# calculate Sigma
sigma=np.zeros((n,n))
for j in range(m):
    if(Y[j]==0):
        sigma+=np.outer(X[j]-u0,X[j]-u0)
    else:
        sigma+=np.outer(X[j]-u1,X[j]-u1)

sigma/=m
print("Sigma=\n{}\n".format(sigma))

sigmainv=np.linalg.inv(sigma)

# Q4(b)&(c)
plt.ion()
fig=plt.figure()
ax1 = fig.add_subplot(1,1,1)

# plots training data and linear separator
def pl():
    ax1.clear()
    ax1.scatter(x4, x5, label= "y='Canada' Training Data", color= "red",
                marker= ".", s=10)
    ax1.scatter(x6, x7, label= "y='Alaska' Training Data", color= "blue",
                marker= ".", s=10)

    xvalues=np.linspace(min(min(x4),min(x6)),max(max(x4),max(x6)),100)

    Xcoeff = np.matmul(sigmainv,(u0-u1)) + np.matmul((u0-u1).T,sigmainv)

    the=[0,0,0]

    # theta[0]=intercept = (constant in Xcoeff) + u1' * inv(sigma) * u1  -  u0' * inv(sigma) * u0  -  2log(phi/(1-phi))
    the[0]=np.matmul(u1.T,np.matmul(sigmainv,u1)) - np.matmul(u0.T,np.matmul(sigmainv,u0)) - 2*math.log((phi/(1-phi)))
    the[1]=Xcoeff[0]
    the[2]=Xcoeff[1]

    yvalues=(-the[1]*xvalues-the[0])/the[2]

    ax1.plot(xvalues, yvalues, label="Linear separator",color ="#66cc00")

    plt.xlabel('x1')
    plt.ylabel('x2')
    plt.legend()
    plt.title('Q4 (b)&(c)')
    plt.show()
    plt.pause(0.001)

# plt.ioff()
pl()

# Q4(d)

sigma0=np.zeros((n,n))
sigma1=np.zeros((n,n))
m0=0
m1=0
for j in range(m):
    if(Y[j]==0):
        sigma0+=np.outer(X[j]-u0,X[j]-u0)
        m0+=1
    else:
        sigma1+=np.outer(X[j]-u1,X[j]-u1)
        m1+=1

sigma0/=m0
sigma1/=m1

print("Sigma0=\n{}\n".format(sigma0))
print("Sigma1=\n{}\n".format(sigma1))

sigma0inv=np.linalg.inv(sigma0)
sigma1inv=np.linalg.inv(sigma1)

# constant = log(det(sigma1)/det(sigma0)) - 2*log(phi/(1-phi))
constant= math.log(abs(np.linalg.det(sigma1)/np.linalg.det(sigma0))) + 2*math.log(phi/(1-phi))

def findval(x,y):
    v = np.array([x,y])
    # (X-u1)' * inv(sigma1) * (X-u1)  -  (X-u0)' * inv(sigma0) * (X-u0)  +  constant
    ans=np.matmul((v-u1).T,np.matmul(sigma1inv,(v-u1))) - np.matmul((v-u0).T,np.matmul(sigma0inv,(v-u0))) + constant
    return ans

# Plots Quadratic Decision boundary as a contour
def plot_2():

    xvalues = np.linspace(min(min(x4),min(x6)),max(max(x4),max(x6)), 100)
    yvalues = np.linspace(min(min(x5),min(x7)),max(max(x5),max(x7)), 100)

    xvalues, yvalues = np.meshgrid(xvalues, yvalues)
    Zs = np.array([findval(x,y) for x,y in zip(np.ravel(xvalues), np.ravel(yvalues))])
    Z = Zs.reshape(xvalues.shape)

    CS=plt.contour(xvalues, yvalues, Z,[0])
    CS.collections[0].set_label("Quadratic boundary")

    plt.xlabel('x1')
    plt.ylabel('x2')
    plt.legend()
    plt.title('Q4 (e)')
    plt.show()
    plt.pause(0.001)

plt.ioff()
plot_2()


# web: https://pythonprogramminglanguage.com/kmeans-elbow-method/
# clustering dataset
# determine k using elbow method

from sklearn.cluster import KMeans
from sklearn import metrics
from scipy.spatial.distance import cdist
import numpy as np
import matplotlib.pyplot as plt

x1 = np.array([3, 1, 1, 2, 1, 6, 6, 6, 5, 6, 7, 8, 9, 8, 9, 9, 8])
x2 = np.array([5, 4, 5, 6, 5, 8, 6, 7, 6, 7, 1, 2, 1, 2, 3, 2, 3])

plt.plot()
plt.xlim([0, 10])
plt.ylim([0, 10])
plt.title('Dataset')
plt.scatter(x1, x2)
plt.show()

# create new plot and data
plt.plot()
X = np.array(list(zip(x1, x2))).reshape(len(x1), 2)
colors = ['b', 'g', 'r']
markers = ['o', 'v', 's']

# k means determine k
distortions = []
K = range(1,10)
for k in K:
    kmeanModel = KMeans(n_clusters=k).fit(X)
    kmeanModel.fit(X)
    distortions.append(sum(np.min(cdist(X, kmeanModel.cluster_centers_, 'euclidean'), axis=1)) / X.shape[0])

# Plot the elbow
plt.plot(K, distortions, 'bx-')
plt.xlabel('k')
plt.ylabel('Distortion')
plt.title('The Elbow Method showing the optimal k')
plt.show()

curso/4 - Machine Learning/Models/kmeans-elbow-method.py


import numpy as np

def train_LDA(X, y):
    '''
    Synopsis:
        w, b= train_LDA(X, y)
    Arguments:
        X: data matrix (features X samples)
        y: labels with values 0 and 1 (1 x samples)
    Output:
        w: LDA weight vector
        b: bias term
    '''
    mu1 = np.mean(X[:, y == 0], axis=1)
    mu2 = np.mean(X[:, y == 1], axis=1)

    ## Three ways to get an estimate of the covariance
    # -- 1. Simply average class-covariance matrices
    #C1 = np.cov(X[:, y==0])
    #C2 = np.cov(X[:, y==1])
    #C = (C1 + C2) / 2
    # -- 2. Weighted average of class-covariance matrices
    #C1 = np.cov(X[:, y==0])
    #C2 = np.cov(X[:, y==1])
    #N1= np.sum(y==0)           # this would be the weighted average
    #N2= np.sum(y==1)
    #C= (N1-1)/(N1+N2-1)*C1 + (N2-1)/(N1+N2-1)*C2
    # -- 3. Center features classwise to estimate covariance on all samples at once
    Xpool = np.concatenate((X[:, y==0]-mu1[:,np.newaxis], X[:, y==1]-mu2[:,np.newaxis]), axis=1)
    C = np.cov(Xpool)

    w = np.linalg.pinv(C).dot(mu2-mu1)
    b = w.T.dot((mu1 + mu2) / 2)
    return w, b

def crossvalidation(classifier_fcn, X, y, folds=10, verbose=False):
    '''
    Synopsis:
        loss_te, loss_tr= crossvalidation(classifier_fcn, X, y, folds=10, verbose=False)
    Arguments:
        classifier_fcn: handle to function that trains classifier as output w, b
        X:              data matrix (features X samples)
        y:              labels with values 0 and 1 (1 x samples)
        folds:         number of folds
        verbose:        print validation results or not
    Output:
        loss_te: value of loss function averaged across test data
        loss_tr: value of loss function averaged across training data
    '''
    nDim, nSamples = X.shape
    inter = np.round(np.linspace(0, nSamples, num=folds + 1)).astype(int)
    perm = np.random.permutation(nSamples)
    errTr = np.zeros([folds, 1])
    errTe = np.zeros([folds, 1])

    for ff in range(folds):
        idxTe = perm[inter[ff]:inter[ff + 1] + 1]
        idxTr = np.setdiff1d(range(nSamples), idxTe)
        w, b = classifier_fcn(X[:, idxTr], y[idxTr])
        out = w.T.dot(X) - b
        errTe[ff] = loss_weighted_error(out[idxTe], y[idxTe])
        errTr[ff] = loss_weighted_error(out[idxTr], y[idxTr])

    if verbose:
        print('{:5.1f} +/-{:4.1f}  (training:{:5.1f} +/-{:4.1f})  [using {}]'.format(errTe.mean(), errTe.std(),
                                                                                errTr.mean(), errTr.std(),
                                                                                classifier_fcn.__name__))
    return np.mean(errTe), np.mean(errTr)

def loss_weighted_error(out, y):
    '''
    Synopsis:
        loss= loss_weighted_error( out, y )
    Arguments:
        out:  output of the classifier
        y:    true class labels
    Output:
        loss: weighted error
    '''
#    loss = 50 * (np.mean(out[y == 0] >= 0) + np.mean(out[y == 1] < 0))
    err1 = 0 if sum(y==0) == 0 else np.mean(out[y == 0] >= 0)
    err2 = 0 if sum(y==1) == 0 else np.mean(out[y == 1] < 0)
    return 100 * np.mean([err1, err2])
    return loss


import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
import plotly.graph_objs as go
import pandas as pd
import sqlite3
from dash.dependencies import Input, Output
import time
# import datetime
from datetime import datetime
from pandas import Series
from scipy import stats
from scipy.stats import norm
from numpy import arange,array,ones
from django_plotly_dash import

app = DjangoDash('DenverTemps', external_stylesheets=[dbc.themes.BOOTSTRAP])
app.config['suppress_callback_exceptions']=True

cnx = sqlite3.connect('denvertemps.db')

# df = pd.read_sql_query("SELECT * FROM temperatures", cnx)
df = pd.read_csv('../../stapleton.csv')

df['datetime']= pd.to_datetime(df['DATE'])
df = df.set_index('datetime')
df_ya_max = df.resample('Y').mean()
# removes final year in df
df5 = df_ya_max[:-1]

# filters all MAXT data for 5 year moving average
allmax_rolling = df['TMAX'].rolling(window=1825)
allmax_rolling_mean = allmax_rolling.mean()

# filters all MINT data fr 5 year moving average
allmin_rolling = df['TMIN'].rolling(window=1825)
allmin_rolling_mean = allmin_rolling.mean()

startyr = 1948
presentyr = datetime.now().year
year_count = presentyr-startyr

xi = arange(0,year_count-1)

years = []
for YEAR in df.index.year.unique():
    years.append({'label':(YEAR), 'value':YEAR})

# linear fit for Avg Max Temps
def annual_min_fit():
    xi = arange(0,year_count)
    slope, intercept, r_value, p_value, std_err = stats.linregress(xi,df5["TMIN"])
    return (slope*xi+intercept)

def annual_max_fit():
    xi = arange(0,year_count)
    slope, intercept, r_value, p_value, std_err = stats.linregress(xi,df5["TMAX"])
    return (slope*xi+intercept)

def all_max_temp_fit():
    xi = arange(0,year_count)
    slope, intercept, r_value, p_value, std_err = stats.linregress(xi,df5["TMAX"])
    return (slope*xi+intercept)

def all_min_temp_fit():
    xi = arange(0,year_count)
    slope, intercept, r_value, p_value, std_err = stats.linregress(xi,df5["TMIN"])
    return (slope*xi+intercept)

years = []
for YEAR in df.index.year.unique():
    years.append({'label':(YEAR), 'value':YEAR})

body = dbc.Container([
    dbc.Row(
        [
            dbc.Col(
                html.H1('DENVER TEMPERATURE RECORD', style={'text-align':'center', 'font-size':50,'font-color':'Gray'})
            )
        ],
        justify='center'
    ),
    dbc.Row(
        [
            dbc.Col(
                html.H2('1948-PRESENT', style={'text-align':'center'})
            )
        ]
    ),
    dbc.Row(
        [
            dbc.Col(
                html.Div(
                    html.H3('MAX DAILY TEMP'),
                style={'text-align':'center'}
                ),
            ),
            dbc.Col(
                html.Div(
                    html.H3('MIN DAILY TEMP'),
                style={'text-align':'center'}
                ),
            ),
        ],
         justify='around',
    ),
    dbc.Row(
        [
            dbc.Col(
                html.Div([
                    dcc.Graph(id='graph1', style={'height':700}),
                ]),
                width={'size':5}
            ),
            dbc.Col(
                html.Div([
                    dcc.Graph(id='graph2', style={'height':700}),
                ]),
                width={'size':5}
            ),
        ],
        justify='around',
    ),
    dbc.Row(
        [
            dbc.Col(
                html.H2('SELECT YEARS', style={'text-align':'center'})
            )
        ]
    ),
    dbc.Row(
        [
            dbc.Col(
                dcc.Dropdown(id='year-picker1', options=years
                ),
                width = {'size': 2}),
            dbc.Col(
                dcc.Dropdown(id='year-picker2', options=years
                ),
                width = {'size': 2}),
        ],
        justify='around',
    ),
    dbc.Row(
        [
            dbc.Col(
                html.H2(id='stats-for-year1',style={'color': 'blue', 'text-align': 'center'}),
                ),
            dbc.Col(
                html.H2(id='stats-for-year2',style={'color': 'darkorange', 'text-align': 'center'}),
                ),
        ],
        justify='around'
    ),
    dbc.Row(
        [
            dbc.Col(
                html.H2(id='Maximum-yearly-temp-1', style={'font-size':25, 'color': 'blue', 'text-align':'center'}),
                width=6, lg=3),
            dbc.Col(
                html.H2(id='Minimum-yearly-temp-1', style={'font-size':25, 'color': 'blue', 'text-align':'center'}),
                width=6, lg=3),
            dbc.Col(
                html.H2(id='Maximum-yearly-temp-2', style={'font-size':25, 'color': 'darkorange', 'text-align':'center'}),
                width=6, lg=3),
            dbc.Col(
                html.H2(id='Minimum-yearly-temp-2', style={'font-size':25, 'color': 'darkorange', 'text-align':'center'}),
                width=6, lg=3),
        ],
    ),
    dbc.Row(
        [
            dbc.Col(
                html.H2(id='90-degree-days-1', style={'font-size':25, 'color': 'blue', 'text-align':'center'}),
                width=6, lg=3),
            dbc.Col(
                html.H2(id='High-below-freezing-1', style={'font-size':25, 'color': 'blue', 'text-align':'center'}),
                width=6, lg=3),
            dbc.Col(
                html.H2(id='90-degree-days-2', style={'font-size':25, 'color': 'darkorange', 'text-align':'center'}),
                width=6, lg=3),
            dbc.Col(
                html.H2(id='High-below-freezing-2', style={'font-size':25, 'color': 'darkorange', 'text-align':'center'}),
                width=6, lg=3),
        ],
    ),
    dbc.Row(
        [
            dbc.Col(
                html.H2(id='80-degree-days-1', style={'font-size':25, 'color': 'blue', 'text-align':'center'}),
                width=6, lg=3),
            dbc.Col(
                html.H2(id='Low-below-zero-1', style={'font-size':25, 'color': 'blue', 'text-align':'center'}),
                width=6, lg=3),
            dbc.Col(
                html.H2(id='80-degree-days-2', style={'font-size':25, 'color': 'darkorange', 'text-align':'center'}),
                width=6, lg=3),
            dbc.Col(
                html.H2(id='Low-below-zero-2', style={'font-size':25, 'color': 'darkorange', 'text-align':'center'}),
                width=6, lg=3),
        ],
    ),
    dbc.Row(
        [
            dbc.Col(
                html.Div([
                    dcc.Graph(id='combined-histogram-max', style={'height':700}),
                ]),
                width = {'size':4},
            ),
            dbc.Col(
                html.Div([
                    dcc.Graph(id='combined-histogram-min', style={'height':700}),
                ]),
                width = {'size': 4},
            )
        ],
        justify='around'
    ),
    dbc.Row(
        [
            dbc.Col(
                html.H3(id='stats-for-year1-1',style={'height':100, 'text-align': 'center'}),
                ),
            dbc.Col(
                html.H3(id='stats-for-year2-2',style={'height':100, 'text-align': 'center'}),
                ),
        ],
        align = 'around'
    ),
    dbc.Row(
        [
            dbc.Col(
                html.Div([
                    dcc.Graph(id='yearly-avg-max-trend', style={'height':700},
                        figure = {
                            'data': [
                                {
                                    'x' : df5.index,
                                    'y' : df5['TMAX'],
                                    'mode' : 'lines + markers',
                                    'name' : 'Max Temp'
                                },
                                {
                                    'x' : df5.index,
                                    'y' : annual_max_fit(),
                                    'name' : 'trend'
                                }
                            ],
                            'layout': go.Layout(
                                xaxis = {'title': 'Date'},
                                yaxis = {'title': 'Temp'},
                                hovermode = 'closest',
                            ),
                        }
                    ),

                ]),
                width = {'size': 4},
            ),
            dbc.Col(
                html.Div([
                    dcc.Graph(id='yearly-avg-min-trend', style={'height':700},
                        figure = {
                            'data': [
                                {
                                    'x' : df5.index,
                                    'y' : df5['TMIN'],
                                    'mode' : 'lines + markers',
                                    'name' : 'Min Temp'
                                },
                                {
                                    'x' : df5.index,
                                    'y' : annual_min_fit(),
                                    'name' : 'trend'
                                }
                            ],
                            'layout': go.Layout(
                                xaxis = {'title': 'Date'},
                                yaxis = {'title': 'Temp'},
                                hovermode = 'closest',
                            ),
                        }
                    ),

                ]),
                width = {'size':4},
            ),
        ],
        justify='around'
    ),
    dbc.Row(
        [
            dbc.Col(
                html.Div(
                    html.H3('DENVER MAX TEMPS, 1948-PRESENT'),
                style={'height':100, 'background-color':'lightsilver', 'text-align': 'center'}
                ),
                width = {'size':5},
            ),
            dbc.Col(
                html.Div(
                    html.H3('DENVER MIN TEMPS, 1948-PRESENT'),
                style={'height':100, 'background-color':'lightsilver', 'text-align': 'center'}
                ),
                width = {'size':5},
            ),
        ],
         justify='around',
    ),
    dbc.Row(
        [
           dbc.Col(
                html.Div([
                    dcc.Graph(id='all-max-temps',
                        figure = {
                            'data': [
                                {
                                    'x' : df.index,
                                    'y' : allmax_rolling_mean,
                                    'mode' : 'lines + markers',
                                    'name' : 'Max Temp'
                                },
                                {
                                    'x' : df5.index,
                                    'y' : all_max_temp_fit(),
                                    'name' : 'trend'
                                }
                            ],
                            'layout': go.Layout(
                                xaxis = {'title': 'Date'},
                                yaxis = {'title': 'Temp'},
                                hovermode = 'closest',
                                height = 1000
                            ),
                        }
                    ),

                ]),
                width = {'size': 8, 'offset':2},
            ),
        ],
    ),
    dbc.Row(
        [
            dbc.Col(
                html.H3('Max Temps 1948-Present, 5 Year Moving Avg', style={'height':100, 'text-align': 'center'}),
                ),
        ],
        align = 'around'
    ),
    dbc.Row(
        [
           dbc.Col(
                html.Div([
                    dcc.Graph(id='all-min-temps',
                        figure = {
                            'data': [
                                {
                                    'x' : df.index,
                                    'y' : allmin_rolling_mean,
                                    'mode' : 'lines + markers',
                                    'name' : 'Max Temp'
                                },
                                {
                                    'x' : df5.index,
                                    'y' : all_min_temp_fit(),
                                    'name' : 'trend'
                                }
                            ],
                            'layout': go.Layout(
                                xaxis = {'title': 'Date'},
                                yaxis = {'title': 'Temp'},
                                hovermode = 'closest',
                                height = 1000
                            ),
                        }
                    ),

                ]),
                width = {'size': 8, 'offset':2},
            ),
        ]
    ),
    dbc.Row(
        [
            dbc.Col(
                html.H3('Min Temps 1948-Present, 5 Year Moving Avg', style={'height':100, 'text-align': 'center'}),
                ),
        ],
        align = 'around'
    ),
    dbc.Row(
        dbc.Col(
            html.Div(id='divider', style={'height':200, 'background-color':'silver'})
                ),
    ),
],
fluid = 'True'
)

@app.callback(Output('graph1', 'figure'),
              [Input('year-picker1', 'value'),
               Input('year-picker2', 'value')])
def update_figure(selected_year1, selected_year2):
    filtered_df1 = df[df.index.year == selected_year1]
    filtered_df2 = df[df.index.year == selected_year2]
    traces = []
    max_rolling = filtered_df1['TMAX'].rolling(window=3)
    min_rolling = filtered_df2['TMAX'].rolling(window=3)
    rolling_max = max_rolling.mean()
    rolling_min = min_rolling.mean()

    traces.append(go.Scatter(
        y = rolling_max,
        name = selected_year1
    ))
    traces.append(go.Scatter(
        y = rolling_min,
        name = selected_year2
    ))

    return {
        'data': traces,
        'layout': go.Layout(
            xaxis = {'title': 'YEAR'},
            yaxis = {'title': 'TMAX'},
            hovermode = 'closest'
        )
    }

@app.callback(Output('graph2', 'figure'),
              [Input('year-picker1', 'value'),
               Input('year-picker2', 'value')])
def update_figure_a(selected_year1, selected_year2):
    filtered_df1 = df[df.index.year == selected_year1]
    filtered_df2 = df[df.index.year == selected_year2]
    traces = []
    max_rolling = filtered_df1['TMIN'].rolling(window=3)
    min_rolling = filtered_df2['TMIN'].rolling(window=3)
    rolling_max = max_rolling.mean()
    rolling_min = min_rolling.mean()

    traces.append(go.Scatter(
        y = rolling_max,
        name = selected_year1
    ))
    traces.append(go.Scatter(
        y = rolling_min,
        name = selected_year2
    ))

    return {
        'data': traces,
        'layout': go.Layout(
            xaxis = {'title': 'YEAR'},
            yaxis = {'title': 'TMAX'},
            hovermode = 'closest'
        )
    }

@app.callback(Output('stats-for-year1', 'children'),
              [Input('year-picker1', 'value')])
def update_layout_i(selected_year1):
    return 'Stats for {}'.format(selected_year1)

@app.callback(Output('stats-for-year2', 'children'),
              [Input('year-picker2', 'value')])
def update_layout_j(selected_year2):
    return 'Stats for {}'.format(selected_year2)

@app.callback(Output('Maximum-yearly-temp-1', 'children'),
              [Input('year-picker1', 'value')])
def update_layout_a(selected_year1):
    filtered_df1 = df[df.index.year == selected_year1]
    annual_max_temp1 = filtered_df1['TMAX'].max()
    return 'Maximum Yearly Temp: {:.0f}'.format(annual_max_temp1)

@app.callback(Output('Minimum-yearly-temp-1', 'children'),
              [Input('year-picker1', 'value')])
def update_layout_b(selected_year1):
    filtered_df1 = df[df.index.year == selected_year1]
    annual_min_temp1 = filtered_df1['TMIN'].min()
    return 'Minimum Yearly Temp: {:.0f}'.format(annual_min_temp1)

@app.callback(Output('Maximum-yearly-temp-2', 'children'),
              [Input('year-picker2', 'value')])
def update_layout_c(selected_year2):
    filtered_df1 = df[df.index.year == selected_year2]
    annual_max_temp2 = filtered_df1['TMAX'].max()
    return 'Maximum Yearly Temp: {:.0f}'.format(annual_max_temp2)

@app.callback(Output('Minimum-yearly-temp-2', 'children'),
              [Input('year-picker2', 'value')])
def update_layout_d(selected_year2):
    filtered_df1 = df[df.index.year == selected_year2]
    annual_min_temp2 = filtered_df1['TMIN'].min()
    return 'Minimum Yearly Temp: {:.0f}'.format(annual_min_temp2)

@app.callback(Output('90-degree-days-1', 'children'),
              [Input('year-picker1', 'value')])
def update_layout_e(selected_year1):
    filtered_df1 = df[df.index.year == selected_year1]
    filtered_df1['datetime'] = pd.to_datetime(filtered_df1['DATE'])
    filtered_df1 = filtered_df1.set_index('datetime')
    df_max = filtered_df1.resample('D').max()
    days_over_90 = (df_max[df_max['TMAX'] >= 90].count()['TMAX'])
    return 'Total Days Above 90 : {}'.format(days_over_90)

@app.callback(Output('High-below-freezing-1', 'children'),
              [Input('year-picker1', 'value')])
def update_layout_f(selected_year1):
    filtered_df1 = df[df.index.year == selected_year1]
    filtered_df1['datetime'] = pd.to_datetime(filtered_df1['DATE'])
    filtered_df1 = filtered_df1.set_index('datetime')
    df_max = filtered_df1.resample('D').max()
    days_high_below_zero = (df_max[df_max['TMAX'] < 32].count()['TMAX'])
    return 'Days High Below Freezing : {:.0f}'.format(days_high_below_zero)

@app.callback(Output('90-degree-days-2', 'children'),
              [Input('year-picker2', 'value')])
def update_layout_g(selected_year2):
    filtered_df1 = df[df.index.year == selected_year2]
    filtered_df1['datetime'] = pd.to_datetime(filtered_df1['DATE'])
    filtered_df1 = filtered_df1.set_index('datetime')
    df_max = filtered_df1.resample('D').max()
    days_over_90 = (df_max[df_max['TMAX'] >= 90].count()['TMAX'])
    return 'Total Days Above 90 : {}'.format(days_over_90)

@app.callback(Output('High-below-freezing-2', 'children'),
              [Input('year-picker2', 'value')])
def update_layout_h(selected_year2):
    filtered_df1 = df[df.index.year == selected_year2]
    filtered_df1['datetime'] = pd.to_datetime(filtered_df1['DATE'])
    filtered_df1 = filtered_df1.set_index('datetime')
    df_max = filtered_df1.resample('D').max()
    days_high_below_zero = (df_max[df_max['TMAX'] < 32].count()['TMAX'])
    return 'Days High Below Freezing : {:.0f}'.format(days_high_below_zero)

@app.callback(Output('80-degree-days-1', 'children'),
              [Input('year-picker1', 'value')])
def update_layout_m(selected_year1):
    filtered_df1 = df[df.index.year == selected_year1]
    filtered_df1['datetime'] = pd.to_datetime(filtered_df1['DATE'])
    filtered_df1 = filtered_df1.set_index('datetime')
    df_max = filtered_df1.resample('D').max()
    days_over_80 = (df_max[df_max['TMAX'] >= 80].count()['TMAX'])
    return 'Total Days Above 80 : {}'.format(days_over_80)

@app.callback(Output('Low-below-zero-1', 'children'),
              [Input('year-picker1', 'value')])
def update_layout_n(selected_year1):
    filtered_df1 = df[df.index.year == selected_year1]
    filtered_df1['datetime'] = pd.to_datetime(filtered_df1['DATE'])
    filtered_df1 = filtered_df1.set_index('datetime')
    df_max = filtered_df1.resample('D').min()
    days_low_below_zero = (df_max[df_max['TMIN'] < 0].count()['TMIN'])
    return 'Days Low Below 0 : {:.0f}'.format(days_low_below_zero)

@app.callback(Output('80-degree-days-2', 'children'),
              [Input('year-picker2', 'value')])
def update_layout_k(selected_year2):
    filtered_df1 = df[df.index.year == selected_year2]
    filtered_df1['datetime'] = pd.to_datetime(filtered_df1['DATE'])
    filtered_df1 = filtered_df1.set_index('datetime')
    df_max = filtered_df1.resample('D').max()
    days_over_80 = (df_max[df_max['TMAX'] >= 80].count()['TMAX'])
    return 'Total Days Above 80 : {}'.format(days_over_80)

@app.callback(Output('Low-below-zero-2', 'children'),
              [Input('year-picker2', 'value')])
def update_layout_l(selected_year2):
    filtered_df1 = df[df.index.year == selected_year2]
    filtered_df1['datetime'] = pd.to_datetime(filtered_df1['DATE'])
    filtered_df1 = filtered_df1.set_index('datetime')
    df_max = filtered_df1.resample('D').min()
    days_low_below_zero = (df_max[df_max['TMIN'] < 0].count()['TMIN'])
    return 'Days Low Below 0 : {:.0f}'.format(days_low_below_zero)

@app.callback(Output('combined-histogram-max','figure'),
              [Input('year-picker1', 'value'),
              Input('year-picker2', 'value')])

def update_graph_a(selected_year1,selected_year2):
    filtered_df1 = df[df.index.year == selected_year1]
    filtered_df1['datetime'] = pd.to_datetime(filtered_df1['DATE'])
    filtered_df1 = filtered_df1.set_index('datetime')
    filtered_df2 = df[df.index.year == selected_year2]
    filtered_df2['datetime'] = pd.to_datetime(filtered_df2['DATE'])
    filtered_df2 = filtered_df2.set_index('datetime')

    trace1 = go.Histogram(
        x=filtered_df1['TMAX'],
        opacity=.5,
        xbins=dict(size=10),
        name = selected_year1
    )

    trace2 = go.Histogram(
        x=filtered_df2['TMAX'],
        opacity=.5,
        xbins=dict(size=10),
        name = selected_year2
    )

    data = [trace1, trace2]

    fig = go.Figure(
        data = data,
        layout = go.Layout(barmode='overlay')
        )
    return fig

# Histogram of minimum daily temps for two selected years
@app.callback(Output('combined-histogram-min','figure'),
              [Input('year-picker1', 'value'),
              Input('year-picker2', 'value')])

def update_graph_b(selected_year1,selected_year2):
    filtered_df1 = df[df.index.year == selected_year1]
    filtered_df1['datetime'] = pd.to_datetime(filtered_df1['DATE'])
    filtered_df1 = filtered_df1.set_index('datetime')
    filtered_df2 = df[df.index.year == selected_year2]
    filtered_df2['datetime'] = pd.to_datetime(filtered_df2['DATE'])
    filtered_df2 = filtered_df2.set_index('datetime')

    trace1 = go.Histogram(
        x = filtered_df1['TMIN'],
        opacity = .5,
        xbins = dict(size = 10),
        name = selected_year1
    )

    trace2 = go.Histogram(
        x = filtered_df2['TMIN'],
        opacity = .5,
        xbins = dict(size=10),
        name = selected_year2
    )

    data = [trace1, trace2]

    fig = go.Figure(
        data = data,
        layout = go.Layout(barmode='overlay')
        )
    return fig

@app.callback(Output('stats-for-year1-1', 'children'),
              [Input('year-picker1', 'value'),
              Input('year-picker2', 'value')])
def update_layout_o(selected_year1, selected_year2):
    return 'Max Temps: {} and {}'.format(selected_year1,selected_year2)

@app.callback(Output('stats-for-year2-2', 'children'),
              [Input('year-picker2', 'value'),
              Input('year-picker1', 'value')])
def update_layout_p(selected_year2, selected_year1):
    return 'Min Temps: {} and {}'.format(selected_year1,selected_year2)

app.layout = html.Div(body)

# if __name__ == "__main__":
#     app.run_server(port=8124, debug=True)


import numpy as np
from prml.nn.tensor.constant import Constant
from prml.nn.tensor.tensor import Tensor
from prml.nn.function import Function

class Abs(Function):

    def forward(self, x):
        x = self._convert2tensor(x)
        self.x = x
        self.output = np.abs(x.value)
        if isinstance(x, Constant):
            return Constant(self.output)
        self.sign = np.sign(x.value)
        return Tensor(self.output, function=self)

    def backward(self, delta):
        dx = self.sign * delta
        self.x.backward(dx)

def abs(x):
    """
    element-wise absolute function
    """
    return Abs().forward(x)


'''
03 - Different linkage, different hierarchical clustering!

In the video, you saw a hierarchical clustering of the voting countries at the Eurovision
song contest using 'complete' linkage. Now, perform a hierarchical clustering of the voting
countries with 'single' linkage, and compare the resulting dendrogram with the one in the
video. Different linkage, different hierarchical clustering!

You are given an array samples. Each row corresponds to a voting country, and each column
corresponds to a performance that was voted for. The list country_names gives the name of
each voting country. This dataset was obtained from Eurovision.

Instructions:

- Import linkage and dendrogram from scipy.cluster.hierarchy.
- Perform hierarchical clustering on samples using the linkage() function with the method='single'
  keyword argument. Assign the result to mergings.
- Plot a dendrogram of the hierarchical clustering, using the list country_names as the labels. In
  addition, specify the leaf_rotation=90, and leaf_font_size=6 keyword arguments as you  have  done
  earlier.
'''
# Perform the necessary imports
import matplotlib.pyplot as plt
from scipy.cluster.hierarchy import linkage, dendrogram

# Calculate the linkage: mergings
mergings = linkage(samples, method='single')

# Plot the dendrogram
dendrogram(mergings,
            labels=country_names,
            leaf_rotation=90,
            leaf_font_size=6)
plt.show()


import numpy as np

# Generate a random Latin Hypercube Design (LHD)

def rLHD(nrows,ncols,unit_cube=False):
  """ Generate a random Latin Hypercube Design (LHD)

  Args:
      nrows (int): A positive integer specifying the number of rows
      ncols (int): A postive integer specifying the number of columns
      unit_cube (bool): If True, design will be in the unit cube [0,1]^ncols.

  Returns:
      numpy.ndarray: return a random (nrows by ncols) LHD

  Examples:
      >>> pyLHD.rLHD(nrows=5,ncols = 4,unit_cube = False)
      >>> pyLHD.rLHD(nrows=5,ncols = 4,unit_cube = True)

  """
  rng = np.random.default_rng()
  rs = np.arange(start=1, stop=nrows+1)
  space = []
  for _ in range(ncols):
    space.append(rng.choice(rs, nrows, replace=False))
  D = np.asarray(space).transpose()

  if unit_cube:
    return (D-0.5)/nrows
  else:
    return D

# Good Lattice Point Design

def GLPdesign(nrows,ncols,h = None):
  """ Good Lattice Point (GLP) Design

  Args:
      nrows (int): A positive integer specifying the number of rows
      ncols (int): A postive integer specifying the number of columns
      h (list, optional): A list whose length is same as (ncols), with its elements that are smaller than and coprime to (nrows).
      Defaults to None. If None, a random sample of (ncols) elements between 1 and (nrows-1).

  Returns:
      numpy.ndarray: A (nrows by ncols) GLP design.

  Examples:
      >>> pyLHD.GLPdesign(nrows=5,ncols=3)
      >>> pyLHD.GLPdesign(nrows=8,ncols=4,h=[1,3,5,7])
  """
  rng = np.random.default_rng()
  if h is None:
    seq = np.arange(start=1,stop=nrows)
    h_sample = rng.choice(seq,ncols,replace=False)
  else:
    h_sample = rng.choice(h,ncols,replace=False)

  mat = np.zeros((nrows,ncols))

  for i in range(nrows):
    for j in range(ncols):
      mat[i,j] = ((i+1)*h_sample[j])% nrows
  return mat.astype(int)


import numpy as np
import sys
from sympy import *
from matplotlib import pyplot as plt

def biseccion(func,rango, tol, iterMax):

    #Funcion de prueba: "E**x - x - 2", [0,2], 10**(-10), 100

    x = Symbol('x') #Inicializa "x" como símbolo
    f = sympify(func) #Se traduce el string "func" a una función de sympy
    fx = lambdify(x, f, modules=['numpy']) #Se inicializa la función f(x)

    D = [] #Datos eje x (Iteraciones)
    A = [] #Datos eje y (Errores)

    cont = 1 #Se inicializa el contador

    #Verifica que la tolerancia no sea negativa
    if (tol < 0):
        return print("La tolerancia debe ser mayor que cero")

    #Verifica que se cumpla con el teorema de Bolzano
    if(not(bolzano(fx(rango[0]),fx(rango[1])))):
        return print("No cumple con el teorema de Bolzano")

    while(cont <= iterMax):

        x = (rango[0] + rango[1]) / 2 #Calcula punto medio
        y = fx(x) #Se calcula el valor de f(x)

        D.append(cont)
        A.append(y)

        if(fx(rango[0])*y < 0):
            rango[1] = x

        elif(y*fx(rango[1]) < 0):
            rango[0] = x

        cont += 1 #Incrementa contador

        if(np.absolute(y) < tol):
            break

    return [x, np.absolute(y), cont, D, A] #Retorna la aproximación del cero de f(x), el error, y la cantidad de iteraciones

def falsa_posicion(func, rango, tol, iterMax):

    #Funcion de prueba: "E**x - x - 2", [0,2], 10**(-10), 100

    x = Symbol('x') #Inicializa "x" como símbolo
    f = sympify(func) #Se traduce el string "func" a una función de sympy
    fx = lambdify(x, f, modules=['numpy']) #Se inicializa la función f(x)

    cont = 0 #Se inicializa el contador

    D = [] #Datos eje x (Iteraciones)
    A = [] #Datos eje y (Errores)

    #Verifica que la tolerancia no sea negativa
    if (tol < 0):
        return print("La tolerancia debe ser mayor que cero")

    #Verifica que se cumpla con el teorema de Bolzano
    if(not(bolzano(fx(rango[0]),fx(rango[1])))):
        return print("No cumple con el teorema de Bolzano")

    b = rango[0]
    a = rango[1]

    xk = a - ((a-b)/(fx(a)-fx(b)))*fx(a)

    D.append(cont)
    A.append(fx(xk))

    while(cont < iterMax):

        if(bolzano(fx(a),fx(xk))):
            if((fx(xk)-fx(a)) == 0): #Indefinición de denominador
                break
            b = xk
            xk = xk - ((xk-a)/(fx(xk)-fx(a))*fx(xk))
        elif(bolzano(fx(xk),fx(b))):
            if((fx(xk)-fx(b)) == 0): #Indefinición de denominador
                break
            a = xk
            xk = xk - ((xk-b)/(fx(xk)-fx(b))*fx(xk))

        D.append(cont)
        A.append(fx(xk))

        cont += 1 #Incrementa contador

        if(np.absolute(fx(xk)) < tol):
            break

    return [xk, np.absolute(fx(xk)), cont, D, A]

def newton_raphson(func, xk, tol, iterMax):

    #Funcion de prueba: "cos(2*x)**2 - x**2", 3/4, 10**(-10), 100

    x = Symbol('x') #Inicializa "x" como símbolo
    f = sympify(func) #Se traduce el string "func" a una función de sympy
    df = f.diff(x) #Se calcula la derivada de "f"

    fx = lambdify(x, f, modules=['numpy']) #Se inicializa la función f(x)
    dfx = lambdify(x, df, modules=['numpy']) #Se inicializa la derivada de la función f(x)

    D = [] #Datos eje x (Iteraciones)
    A = [] #Datos eje y (Errores)

    cont = 1 #Se inicializa el contador

    #Verifica que la tolerancia no sea negativa
    if (tol < 0):
        return print("La tolerancia debe ser mayor que cero")

    while(cont <= iterMax):

        if(dfx(xk) == 0): #Indefinición de denominador
            break

        xk = xk - ((fx(xk))/(dfx(xk)))
        y = fx(xk)

        D.append(cont)
        A.append(y)

        if(np.absolute(y) < tol):
            break

        cont += 1 #Incrementa contador

    return [xk, np.absolute(y), cont, D, A] #Retorna la aproximación del cero de la función, el error, y la cantidad de iteraciones

def secante(func, x0, x1, tol, iterMax):

    #Función de prueba: "cos(2*x)**2 - x**2", 3/4, 1/2, 10**(-10), 100

    x = Symbol('x') #Inicializa "x" como símbolo
    f = sympify(func) #Se traduce el string "func" a una función de sympy
    fx = lambdify(x, f, modules=['numpy']) #Se inicializa la función f(x)

    cont = 1 #Se inicializa el contador

    D = [] #Datos eje x (Iteraciones)
    A = [] #Datos eje y (Errores)

    #Verifica que la tolerancia no sea negativa
    if (tol < 0):
        return print("La tolerancia debe ser mayor que cero")

    while(cont <= iterMax):

        if((fx(x1)-fx(x0)) == 0): #Indefinición de denominador
            break

        xk = x1 - ((x1-x0)/(fx(x1)-fx(x0)))*fx(x1) #Se calcula xk
        error = np.absolute(fx(xk)) #Se calcula el error

        D.append(cont)
        A.append(error) #Se añade el error a la lista de errores

        if(error < tol):
            break

        x0 = x1 #Reasigna x0
        x1 = xk #Reasigna x1

        cont += 1 #Incrementa contador

    return [xk, np.absolute(fx(xk)), cont, D, A] #Retorna xk, las iteraciones, y f(x)

def graphicPlot(D, A):

    #Funcion utilizada para graficar
    plt.figure("Grafica")
    plt.plot(np.array(D), np.array(A))
    plt.title("Gráfico Iteraciones - Errores")
    plt.xlabel('Iteraciones')
    plt.ylabel('Error')
    plt.legend(["Error"])
    plt.show()

def bolzano(x0,x1):

    #Verificacion del teorema de bolzano
    if(x0*x1 < 0):
        return True
    else:
        return False


import numpy as np

A = np.array([[2, -3, -1], [3, 2, -5], [2, 4, -1]])
print('A:', A)

b = np.array([3, -9, -5])
print('b:', b)

n = len(b)
x = np.zeros(n)

for k in range(n - 1):
    for i in range(k + 1, n):
        if A[i, k] == 0:continue
        factor = A[k, k] / A[i, k]
        for j in range(k, n):
            A[i, j] = A[k, j] - A[i, j] * factor
        b[i] = b[k] - b[i] * factor

x[n - 1] = b[n - 1] / A[n - 1, n - 1]
for i in range(n - 2, -1, -1):
    sum_Ax = 0
    for j in range(i + 1, n):
        sum_Ax += A[i, j] * x[j]
    x[i] = (b[i] - sum_Ax) / A[i, i]

print('***SOLUTION***')
print('x:', x)


import numpy as np

"""
A library of activation functions used in neural networks.
"""

def softmax(x):
	e_x = np.exp(x - np.max(x))
	return e_x / e_x.sum()

def leaky_ReLu(x, alpha=-0.05):
	return np.where(x > 0, x, alpha * x)

def ReLu(x):
	return np.where(x > 0, x, 0)

def tanh(x):
	return np.tanh(x)


import pandas as pd
import numpy as np
from tqdm import tqdm
import os
from pathlib import Path
import argparse
from scipy.spatial import distance
from math import factorial, atan2, degrees, acos, sqrt, pi

from deeplabcut.utils import auxiliaryfunctions

# UTILS FUNCTIONS
def calc_distance_between_points_two_vectors_2d(v1, v2):
    '''calc_distance_between_points_two_vectors_2d [pairwise distance between vectors points]

    Arguments:
        v1 {[np.array]} -- [description]
        v2 {[type]} -- [description]

    Raises:
        ValueError -- [description]
        ValueError -- [description]
        ValueError -- [description]

    Returns:
        [type] -- [description]

    testing:
    >>> v1 = np.zeros((2, 5))
    >>> v2 = np.zeros((2, 5))
    >>> v2[1, :]  = [0, 10, 25, 50, 100]
    >>> d = calc_distance_between_points_two_vectors_2d(v1.T, v2.T)
    '''

    # Check dataformats
    if not isinstance(v1, np.ndarray) or not isinstance(v2, np.ndarray):
        raise ValueError('Invalid argument data format')
    if not v1.shape[1] == 2 or not v2.shape[1] == 2:
        raise ValueError('Invalid shape for input arrays')
    if not v1.shape[0] == v2.shape[0]:
        raise ValueError('Error: input arrays should have the same length')

    # Calculate distance
    dist = [distance.euclidean(p1, p2) for p1, p2 in zip(v1, v2)]
    return dist

def angle_between_points_2d_anticlockwise(p1, p2):
    '''angle_between_points_2d_clockwise [Determines the angle of a straight line drawn between point one and two.
        The number returned, which is a double in degrees, tells us how much we have to rotate
        a horizontal line anti-clockwise for it to match the line between the two points.]

    Arguments:
        p1 {[np.ndarray, list]} -- np.array or list [ with the X and Y coordinates of the point]
        p2 {[np.ndarray, list]} -- np.array or list [ with the X and Y coordinates of the point]

    Returns:
        [int] -- [clockwise angle between p1, p2 using the inner product and the deterinant of the two vectors]

    Testing:  - to check:     print(zero, ninety, oneeighty, twoseventy)
        >>> zero = angle_between_points_2d_clockwise([0, 1], [0, 1])
        >>> ninety = angle_between_points_2d_clockwise([1, 0], [0, 1])
        >>> oneeighty = angle_between_points_2d_clockwise([0, -1], [0, 1])
        >>> twoseventy = angle_between_points_2d_clockwise([-1, 0], [0, 1])
        >>> ninety2 = angle_between_points_2d_clockwise([10, 0], [10, 1])
        >>> print(ninety2)
    '''

    """
        Determines the angle of a straight line drawn between point one and two.
        The number returned, which is a double in degrees, tells us how much we have to rotate
        a horizontal line anit-clockwise for it to match the line between the two points.
    """

    xDiff = p2[0] - p1[0]
    yDiff = p2[1] - p1[1]
    ang = degrees(atan2(yDiff, xDiff))
    if ang < 0: ang += 360
    # if not 0 <= ang <+ 360: raise ValueError('Ang was not computed correctly')
    return ang

def calc_angle_between_vectors_of_points_2d(v1, v2):
    '''calc_angle_between_vectors_of_points_2d [calculates the clockwise angle between each set of point for two 2d arrays of points]

    Arguments:
        v1 {[np.ndarray]} -- [2d array with X,Y position at each timepoint]
        v2 {[np.ndarray]} -- [2d array with X,Y position at each timepoint]

    Returns:
        [np.ndarray] -- [1d array with clockwise angle between pairwise points in v1,v2]

    Testing:
    >>> v1 = np.zeros((2, 4))
    >>> v1[1, :] = [1, 1, 1, 1, ]
    >>> v2 = np.zeros((2, 4))
    >>> v2[0, :] = [0, 1, 0, -1]
    >>> v2[1, :] = [1, 0, -1, 0]
    >>> a = calc_angle_between_vectors_of_points_2d(v2, v1)
    '''

    # Check data format
    if v1 is None or v2 is None or not isinstance(v1, np.ndarray) or not isinstance(v2, np.ndarray):
        raise ValueError('Invalid format for input arguments')
    if len(v1) != len(v2):
        raise ValueError('Input arrays should have the same length, instead: ', len(v1), len(v2))
    if not v1.shape[0] == 2 or not v2.shape[0] == 2:
        raise ValueError('Invalid shape for input arrays: ', v1.shape, v2.shape)

    # Calculate
    n_points = v1.shape[1]
    angs = np.zeros(n_points)
    for i in range(v1.shape[1]):
        p1, p2 = v1[:, i], v2[:, i]
        angs[i] = angle_between_points_2d_anticlockwise(p1, p2)

    return angs

# Process single bone
def analyzebone(bp1, bp2):
    """[Computes length and orientation of the bone at each frame]

    Arguments:
        bp1 {[type]} -- [description]
        bp2 {[type]} -- [description]
    """
    bp1_pos = np.vstack([bp1.x.values, bp1.y.values]).T
    bp2_pos = np.vstack([bp2.x.values, bp2.y.values]).T

    # get bone length and orientation
    bone_length = calc_distance_between_points_two_vectors_2d(bp1_pos, bp2_pos)
    bone_orientation = calc_angle_between_vectors_of_points_2d(bp1_pos.T, bp2_pos.T)

    # keep the smallest of the two likelihoods
    likelihoods = np.vstack([bp2.likelihood.values, bp2.likelihood.values]).T
    likelihood = np.min(likelihoods, 1)

    # Create dataframe and return
    df = pd.DataFrame.from_dict(dict(
                                    length=bone_length,
                                    orientation=bone_orientation,
                                    likelihood=likelihood,
                                    ))
    # df.index.name=name

    return df

# MAIN FUNC
def analyzeskeleton(config, videos, videotype='avi', shuffle=1, trainingsetindex=0, save_as_csv=False, destfolder=None):
    """
    Extracts length and orientation of each "bone" of the skeleton as defined in the config file.

    Parameter
    ----------
    config : string
        Full path of the config.yaml file as a string.

    videos : list
        A list of strings containing the full paths to videos for analysis or a path to the directory, where all the videos with same extension are stored.

    shuffle : int, optional
        The shufle index of training dataset. The extracted frames will be stored in the labeled-dataset for
        the corresponding shuffle of training dataset. Default is set to 1

    trainingsetindex: int, optional
        Integer specifying which TrainingsetFraction to use. By default the first (note that TrainingFraction is a list in config.yaml).

    save_as_csv: bool, optional
        Saves the predictions in a .csv file. The default is ``False``; if provided it must be either ``True`` or ``False``

    destfolder: string, optional
        Specifies the destination folder for analysis data (default is the path of the video). Note that for subsequent analysis this
        folder also needs to be passed.
    """
    # Load config file, scorer and videos
    cfg = auxiliaryfunctions.read_config(config)
    scorer=auxiliaryfunctions.GetScorerName(cfg,shuffle,trainFraction = cfg['TrainingFraction'][trainingsetindex])

    Videos=auxiliaryfunctions.Getlistofvideos(videos,videotype)
    for video in Videos:
        print(video)
        if destfolder is None:
            videofolder = str(Path(video).parents[0])
        else:
            videofolder=destfolder

        print("Processing %s"%(video))

        dataname = str(Path(video).stem)+scorer
        processedname=dataname.split('.h5')[0]+'_skeleton.h5'
        try:
            # See if file was already proccessed
            Dataframe = pd.read_hdf(os.path.join(videofolder, processedname))
            print("Video already processed...")
        except FileNotFoundError:
            try:
                Dataframe = pd.read_hdf(os.path.join(videofolder,dataname+'.h5'))
                # Process skeleton
                bones = {}
                for bp1, bp2 in cfg['skeleton']:
                    name = "{}_{}".format(bp1, bp2)
                    bones[name] = analyzebone(Dataframe[scorer][bp1], Dataframe[scorer][bp2])

                skeleton = pd.concat(bones, axis=1)
                # save
                skeleton.to_hdf(processedname, 'df_with_missing', format='table', mode='w')
                if save_as_csv:
                    skeleton.to_csv(processedname.split('.h5')[0]+'.csv')

            except FileNotFoundError:
                print("Video not analyzed -- Run analyze_videos first.")

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('config')
    parser.add_argument('videos')
    cli_args = parser.parse_args()


import numpy as np
import numpy.testing as npt
from toppra import PolynomialPath

def test_scalar():
    """Scalar case."""
    pi = PolynomialPath([1, 2, 3], s_start=0, s_end=2)  # 1 + 2s + 3s^2
    assert pi.dof == 1
    npt.assert_allclose(pi.eval([0, 0.5, 1]), [1, 2.75, 6])
    npt.assert_allclose(pi.evald([0, 0.5, 1]), [2, 5, 8])
    npt.assert_allclose(pi.evaldd([0, 0.5, 1]), [6, 6, 6])
    npt.assert_allclose(pi.path_interval, np.r_[0, 2])

def test_2_dof():
    """Polynomial path with 2dof."""
    pi = PolynomialPath([[1, 2, 3], [-2, 3, 4, 5]])
    # [1 + 2s + 3s^2]
    # [-2 + 3s + 4s^2 + 5s^3]
    assert pi.dof == 2
    npt.assert_allclose(
        pi.eval([0, 0.5, 1]), [[1, -2], [2.75, 1.125], [6, 10]])
    npt.assert_allclose(
        pi.evald([0, 0.5, 1]), [[2, 3], [5, 10.75], [8, 26]])
    npt.assert_allclose(pi.evaldd([0, 0.5, 1]), [[6, 8], [6, 23], [6, 38]])


from sympy import *
import math

def newton_iter(f, x_0, eps=0, eta=0, df=None, max_steps=20, frac=False, verbose=False):
    """牛顿迭代法

    Args:
        f: function, 迭代函数
        x_0: float, 初值
        eps: float, 根的容许误差, default 0.
        eta: float, abs(f(x)) 的容许误差, default 0.

        df: function, f 的导函数, 默认 None 表示自动调用 sympy.diff 求导(会导致后续迭代中使用分数运算)。
        max_steps: int, 最大迭代次数, default 20.
        frac: bool, True 则输出分数(仅对 df=None 时生效)，否则使用 float, default False.
        verbose: bool, 打印出每一步的值, default False.

    Returns:
        x_final: float, 最终的近似根 x
    """

    if df == None:
        __x = Symbol('x')
        __df = diff(f(__x), __x)

        df = lambda x: __df.subs(__x, x)

    x = x_0
    for i in range(max_steps):
        if verbose:
            print(i, x)

        x_next = x - f(x) / df(x)

        if abs(x_next - x) <= eps or abs(f(x)) <= eta:
            break

        x = x_next

    if not frac:
        x = float(x)
    return x


"""
  Name     : c11_17_modifed_VaR_one_day.py
  Book     : Python for Finance (2nd ed.)
  Publisher: Packt Publishing Ltd.
  Author   : Yuxing Yan
  Date     : 6/6/2017
  email    : yany@canisius.edu
             paulyxy@hotmail.com
"""
import numpy as np
import pandas as pd
from scipy.stats import stats,norm
from matplotlib.finance import quotes_historical_yahoo_ochl as getData
#
ticker='WMT'            # input 1
n_shares=500            # input 2
confidence_level=0.99   # input 3
begdate=(2000,1,1)      # input 4
enddate=(2016,12,31)    # input 5
#
# Method I: based on the first two moments
z=abs(norm.ppf(1-confidence_level))
x=getData(ticker,begdate,enddate,asobject=True,adjusted=True)
ret = x.aclose[1:]/x.aclose[:-1]-1
position=n_shares*x.close[0]
mean=np.mean(ret)
std=np.std(ret)
VaR1=position*(mean-z*std)
print("Holding=",round(position,2), "VaR1=", round(VaR1,2), "for 1 day ")
#
# Modified VaR: based on 4 moments
s=stats.skew(ret)
k=stats.kurtosis(ret)
t=z+1/6.*(z**2-1)*s+1/24.*(z**3-3*z)*k-1/36.*(2*z**3-5*z)*s**2
mVaR=position*(mean-t*std)
print("Holding=",round(position,2), "modified VaR=", round(mVaR,2), "for 1 day ")


import numpy as np
import matplotlib.pyplot as plt

# rosenbrock function
# from http://docs.scipy.org/doc/scipy-0.14.0/reference/tutorial/optimize.html
def rosenbrock(x):
  return sum(100.0*(x[1:]-x[:-1]**2.0)**2.0 + (1-x[:-1])**2.0)

ub = 3.0
lb = -3.0

d = 2 # dimension of decision variable space
num_seeds = 30

popsize = 10
CR = 0.9 # crossover probability
F = 0.9 # between 0 and 2, vector step
max_NFE = 10000 # should be a multiple
convergence_threshold = 10**-6
nfe_to_converge = []

# differential evolution (a simple version)
for seed in range(num_seeds):
  np.random.seed(seed)

  # random initial population (popsize x d matrix)
  P = np.random.uniform(lb, ub, (popsize,d))
  f = np.zeros(popsize) # we'll evaluate them later
  nfe = 0
  f_best, x_best = 9999999, None
  ft = []

  while nfe < max_NFE and f_best > convergence_threshold:

    # for each member of the population ..
    for i,x in enumerate(P):

      # pick two random population members
      # "x" will be the one we modify, but other variants
      # will always modify the current best solution instead
      xb,xc = P[np.random.randint(0, popsize, 2), :]
      v = x + F*(xb-xc) # mutant vector

      # crossover: either choose from x or v
      trial_x = np.copy(x)
      for j in range(d):
        if np.random.rand() < CR:
          trial_x[j] = v[j]

      f[i] = rosenbrock(x)
      trial_f = rosenbrock(trial_x)
      nfe += 1

      # if this is better than the parent, replace
      if trial_f < f[i]:
        P[i,:] = trial_x
        f[i] = trial_f

    # keep track of best here
    if f_best is None or f.min() < f_best:
      f_best = f.min()
      x_best = P[f.argmin(),:]

    ft.append(f_best)

  nfe_to_converge.append(nfe)
  fta = np.array(ft)
  plt.subplot(1,2,1)
  plt.semilogy(range(popsize,nfe+1,popsize), fta, color='steelblue', linewidth=1)
  plt.subplot(1,2,2)
  plt.plot(range(2*popsize,nfe+1,popsize), fta[1:]/fta[:-1], color='steelblue', linewidth=1)
  del ft[:]

print(nfe_to_converge)

plt.subplot(1,2,1)
plt.xlabel('NFE')
plt.ylabel('Objective Value')

plt.subplot(1,2,2)
plt.xlabel('NFE')
plt.ylabel('$f_{t} / f_{t-1}$')
plt.show()


"""

https://en.wikipedia.org/wiki/Routh%E2%80%93Hurwitz_stability_criterion
http://et.engr.iupui.edu/~skoskie/ECE680/Routh.pdf
http://control.asu.edu/Classes/MAE318/318Lecture10.pdf
http://blog.codelv.com/2013/02/control-systems-in-python-part-2.html

"""

from sympy import *
from sympy.abc import *

"""

A system is considered stable if the poles of its transfer functions all have negative real part
A way to figure this out for large polynomials is to use the routh table

A routh table is the following table
s^n    a0 a2 a4 a6 ...
s^n-1  a1 a3 a5 a7 ...
s^n-2  b1 b2 b3 b4 ...
s^n-3  c1 c2 c3 c4 ...
...
s^2    e1 e2
s^1    f1
s^0    g0

Where the coefficients are
b1 = (a1*a2-a0*a3)/a1
b2 = (a1*a4-a0*a5)/a1
b3 = (a1*a6-a0*a7)/a1
...
c1 = (b1*a3-a1*b2)/b1
c2 = (b1*a5-a1*b3)/b1
c3 = (b1*a7-a1*b4)/b1
...
d1 = (c1*b2-b1*c2)/c1
d2 = (c1*b3-b1*c3)/c1

Then verify the table has all positive coefficients in the first row (all coefficients of a are positive)
and all the coefficient in the first column be positive, if there are any sign changes in the column
they tell us that there is a root that has positive real part (make it unstable)

"""

# does not work with all zero rows
def routh_hurwitz(Ts,*args):
    den = Poly(Ts.as_numer_denom()[1],s).all_coeffs()
    n = len(den)-1
    if n < 2:
        return None
    m = zeros(n + 1)

    c = [den[i] for i in range(0, n + 1, 2)]
    for i in range(0,len(c)):
        m[0, i] = c[i]

    c = [den[i] for i in range(1, n + 1, 2)]
    for i in range(0,len(c)):
        m[1, i] = c[i]

    for i in range(2, n+1):
        for j in range(1, n):
            if m[i-1,j] == 0:
                m[i, j-1] = m[i-2, j]
            elif m[i-1, 0] != 0:
                m[i, j-1] = ((m[i-1, 0]*m[i-2, j]-m[i-2, 0]*m[i-1, j])/m[i-1, 0]).together().simplify()

    m = m.col_insert(0, Matrix(([s**(n-i) for i in range(0,n+1)])))
    return m

# stable
pprint(routh_hurwitz(1/(s**4 + 2*s**3 + 3*s**2 + s + 1)))
pprint(routh_hurwitz(1/(s*s*s + a*s*s + b*s + c)))
pprint(routh_hurwitz((k*s**2 + s + 1)/(s**4 + 2*s**3 + (3+k)*s**2 + (1+k)*s + (1+k))))

# unstable
pprint(routh_hurwitz(1/(s**3 + s**2 + s + 2)))
pprint(routh_hurwitz(1000/(s**3 + 10*s**2 + 31*s + 1030)))

# should fail on this one since it has some all zeros row
pprint(routh_hurwitz(1/(s**5 + 2*s**4 + 24*s**3 + 48*s**2 - 50)))


import math
import multiprocessing as mp
import random
import sys
import time

import numpy as np
import pandas as pd

# List comprehension

a = [random.randint(1, 100) for _ in range(1000000)]
b = [random.randint(1, 100) for _ in range(1000000)]

start_time = time.perf_counter()

[x * y for x, y in zip(a, b)]

print(f"--- List comprehension ---\n{time.perf_counter() - start_time} seconds ")

# Vectorized with numpy

a = np.array([random.randint(1, 100) for _ in range(1000000)])
b = np.array([random.randint(1, 100) for _ in range(1000000)])

start_time = time.perf_counter()

a * b

# Harversine formula (great circle distance of two points on Earth)

def haversine(row):
    """
    Non-vectorized harversine
    """
    a_lat, a_lng, b_lat, b_lng = row
    R = 6371  # earth radius in km

    a_lat = math.radians(a_lat)
    a_lng = math.radians(a_lng)
    b_lat = math.radians(b_lat)
    b_lng = math.radians(b_lng)

    d_lat = b_lat - a_lat
    d_lng = b_lng - a_lng

    a = math.pow(math.sin(d_lat / 2), 2) + math.cos(a_lat) * math.cos(b_lat) * math.pow(
        math.sin(d_lng / 2), 2
    )
    c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))

    return R * c

n_values = 10000

coords = np.array(
    [
        np.random.uniform(-90, 90, n_values),  # random origin latitude
        np.random.uniform(-180, 180, n_values),  # random origin longitude
        np.random.uniform(-90, 90, n_values),  # random destination latitude
        np.random.uniform(-180, 180, n_values), # random destination longitude
    ]
).T

start_time = time.perf_counter()

np.apply_along_axis(haversine, 1, coords)

print(
    f"--- Non-vectorized harversine ---\n{time.perf_counter() - start_time} seconds "
)

def vec_haversine(a_lat, a_lng, b_lat, b_lng):
    """
    Vectorized harversine
    """
    R = 6371  # earth radius in km

    a_lat = np.radians(a_lat)
    a_lng = np.radians(a_lng)
    b_lat = np.radians(b_lat)
    b_lng = np.radians(b_lng)

    d_lat = b_lat - a_lat
    d_lng = b_lng - a_lng

    d_lat_sq = np.sin(d_lat / 2) ** 2
    d_lng_sq = np.sin(d_lng / 2) ** 2

    a = d_lat_sq + np.cos(a_lat) * np.cos(b_lat) * d_lng_sq
    c = 2 * np.arctan2(np.sqrt(a), np.sqrt(1 - a))

    return R * c  # returns distance between a and b in km

start_time = time.perf_counter()

vec_haversine(coords[:, 0], coords[:, 1], coords[:, 2], coords[:, 3])

print(
    f"--- Vectorized harversine ---\n{time.perf_counter() - start_time} seconds "
)

# Harversine with pandas

df_coords = pd.DataFrame(
    coords,
    columns=["origin_lat", "origin_lng", "destination_lat", "destination_lng"],
)

start_time = time.perf_counter()

vec_haversine(
    df_coords.origin_lat,
    df_coords.origin_lng,
    df_coords.destination_lat,
    df_coords.destination_lng,
)

print(
    f"--- Vectorized harversine on dataframe ---\n{time.perf_counter() - start_time} seconds "
)

# Multi-processing

def process_chunk(proc_chunk):
    chunk_res = vec_haversine(
        proc_chunk.origin_lat,
        proc_chunk.origin_lng,
        proc_chunk.destination_lat,
        proc_chunk.destination_lng,
    )
    chunk_res.index = proc_chunk.index

    return chunk_res

n_proc = mp.cpu_count()

start_time = time.perf_counter()

chunksize = len(df_coords)

proc_chunks = []
for i_proc in range(n_proc):
    chunkstart = i_proc * chunksize
    # make sure to include the division remainder for the last process
    chunkend = (i_proc + 1) * chunksize if i_proc < n_proc - 1 else None

    proc_chunks.append(df_coords.iloc[slice(chunkstart, chunkend)])

# check whether all data are assigned to a process
assert sum(map(len, proc_chunks)) == len(df_coords)

with mp.Pool(processes=n_proc) as pool:
    # starts the sub-processes without blocking
    # pass the chunk to each worker process
    proc_results = [
        pool.apply_async(process_chunk, args=(chunk,)) for chunk in proc_chunks
    ]

    # blocks until all results are fetched
    result_chunks = [r.get() for r in proc_results]

results = pd.concat(result_chunks)
results = pd.concat((df_coords, results), axis=1)
# make sure we got a result for each coordinate pair:
assert len(results) == len(df_coords)

print(f"--- Parallel processes ---\n{time.perf_counter() - start_time} seconds ")


import numpy as np

def Normalization(nd, x):
    '''
    Normalization of coordinates (centroid to the origin and mean distance of sqrt(2 or 3).

    Input
    -----
    nd: number of dimensions, 3 here
    x: the data to be normalized (directions at different columns and points at rows)
    Output
    ------
    Tr: the transformation matrix (translation plus scaling)
    x: the transformed data
    '''

    x = np.asarray(x)
    m, s = np.mean(x, 0), np.std(x)
    if nd == 2:
        Tr = np.array([[s, 0, m[0]], [0, s, m[1]], [0, 0, 1]])
    else:
        Tr = np.array([[s, 0, 0, m[0]], [0, s, 0, m[1]], [0, 0, s, m[2]], [0, 0, 0, 1]])

    Tr = np.linalg.inv(Tr)
    x = np.dot( Tr, np.concatenate( (x.T, np.ones((1,x.shape[0]))) ) )
    x = x[0:nd, :].T

    return Tr, x

def DLTcalib(nd, xyz, uv):
    '''
    Camera calibration by DLT using known object points and their image points.

    Input
    -----
    nd: dimensions of the object space, 3 here.
    xyz: coordinates in the object 3D space.
    uv: coordinates in the image 2D space.

    The coordinates (x,y,z and u,v) are given as columns and the different points as rows.

    There must be at least 6 calibration points for the 3D DLT.

    Output
    ------
     L: array of 11 parameters of the calibration matrix.
     err: error of the DLT (mean residual of the DLT transformation in units of camera coordinates).
    '''
    if (nd != 3):
        raise ValueError('%dD DLT unsupported.' %(nd))

    # Converting all variables to numpy array
    xyz = np.asarray(xyz)
    uv = np.asarray(uv)

    n = xyz.shape[0]

    # Validating the parameters:
    if uv.shape[0] != n:
        raise ValueError('Object (%d points) and image (%d points) have different number of points.' %(n, uv.shape[0]))

    if (xyz.shape[1] != 3):
        raise ValueError('Incorrect number of coordinates (%d) for %dD DLT (it should be %d).' %(xyz.shape[1],nd,nd))

    if (n < 6):
        raise ValueError('%dD DLT requires at least %d calibration points. Only %d points were entered.' %(nd, 2*nd, n))

    # Normalize the data to improve the DLT quality (DLT is dependent of the system of coordinates).
    # This is relevant when there is a considerable perspective distortion.
    # Normalization: mean position at origin and mean distance equals to 1 at each direction.
    Txyz, xyzn = Normalization(nd, xyz)
    Tuv, uvn = Normalization(2, uv)

    A = []

    for i in range(n):
        x, y, z = xyzn[i, 0], xyzn[i, 1], xyzn[i, 2]
        u, v = uvn[i, 0], uvn[i, 1]
        A.append( [x, y, z, 1, 0, 0, 0, 0, -u * x, -u * y, -u * z, -u] )
        A.append( [0, 0, 0, 0, x, y, z, 1, -v * x, -v * y, -v * z, -v] )

    # Convert A to array
    A = np.asarray(A)

    # Find the 11 parameters:
    U, S, V = np.linalg.svd(A)

    # The parameters are in the last line of Vh and normalize them
    L = V[-1, :] / V[-1, -1]
    print(L)
    # Camera projection matrix
    H = L.reshape(3, nd + 1)
    print(H)

    # Denormalization
    # pinv: Moore-Penrose pseudo-inverse of a matrix, generalized inverse of a matrix using its SVD
    H = np.dot( np.dot( np.linalg.pinv(Tuv), H ), Txyz )
    print(H)
    H = H / H[-1, -1]
    print(H)
    L = H.flatten(0)
    print(L)

    # Mean error of the DLT (mean residual of the DLT transformation in units of camera coordinates):
    uv2 = np.dot( H, np.concatenate( (xyz.T, np.ones((1, xyz.shape[0]))) ) )
    uv2 = uv2 / uv2[2, :]
    # Mean distance:
    err = np.sqrt( np.mean(np.sum( (uv2[0:2, :].T - uv)**2, 1)) )

    return L, err

def DLT():
    # Known 3D coordinates
    xyz = [[-875, 0, 9.755], [442, 0, 9.755], [1921, 0, 9.755], [2951, 0.5, 9.755], [-4132, 0.5, 23.618],
    [-876, 0, 23.618]]
    # Known pixel coordinates
    uv = [[76, 706], [702, 706], [1440, 706], [1867, 706], [264, 523], [625, 523]]

    nd = 3
    P, err = DLTcalib(nd, xyz, uv)
    print('Matrix')
    print(P)
    print('\nError')
    print(err)

if __name__ == "__main__":
    DLT()


# 28 February 2018 Miroslav Gasparek
# Python tutorial on random number generation

import random
import numpy as np

import matplotlib.pyplot as plt
# Some pretty Seaborn settings
import seaborn as sns
rc={'lines.linewidth': 2, 'axes.labelsize': 18, 'axes.titlesize': 18}
sns.set(rc=rc)

# Generate sorted random numbers
x = np.sort(np.random.random(size=100000))
# Generate y-axis for CDF
y = np.arange(1, len(x)+1)/len(x)
# Plot CDF from random numbers (only 100 points for the plotting purposes)
fig1 = plt.figure(1)
plt.plot(x[::1000], y[::1000], marker='.', linestyle='none', markersize=10)
# Plot expected CDF (just a straight line from (0,0) to (1,1) )
plt.plot([0, 1], [0, 1], 'k--')
fig1.show()

# Doing 'coin flips'
# Generate 20 random numbers on uniform interval
# x = np.random.random(size=20)
# # Make them coin flips
# heads = x > 0.5
# # Show which were heads, and count the number of heads
# print(heads)
# print('\nThere were', np.sum(heads), ' heads.')

# Seed the RNG
np.random.seed(42)
# Generate random numbers
rand_ar1 = np.random.random(size=10)

# Re-seed the RNG
np.random.seed(42)
# Generate random numbers
rand_ar2 = np.random.random(size=10)

# Seed with a number that is close to the answer to everything
np.random.seed(43)
rand_ar3 = np.random.random(size=10)

# Set parameters
mu = 10
sigma = 1
# Draw 10000 random samples
x = np.random.normal(mu, sigma, size=10000)
# Plot a histogram of our draws
fig2 = plt.figure(2)
_ = plt.hist(x, bins=100)
fig2.show()

sample_mean = np.mean(x)
sample_std = np.std(x)

# Selections from discrete distributions
# Draw random integers on [0, 4), i. e., exclusive of last one.
rand_ar_int = np.random.randint(0, 4, 20)

# This can be used to generate random DNA sequences
# Key of bases
bases = 'ATGC'
# Draw random numbers for sequence
x = np.random.randint(0, 4, 50)
# Make sequence
seq_list = [None]*50
for i,b in enumerate(x):
    seq_list[i] = bases[b]
# Join the sequence
seq = ''.join(seq_list)

# Choosing elements from an array
# Choose elements from array without replacement
rand_ar4 = np.random.choice(np.arange(51), 20, replace=False)
print(rand_ar4)
# Shuffiling an array
rand_ar5 = np.random.permutation(np.arange(53))
print('\n', rand_ar5)

# Random module
# Make sequence
seq_list = [None]*50
for i in range(len(seq_list)):
    seq_list[i] = random.choice('ATGC')

# Join the sequence
seq2 = ''.join(seq_list)


from scipy.stats import moment
import math as math
import mgt2001.per as per

def interval(df):
    upl_1 = round(df.mean() + df.std(), 4)
    upl_2 = round(df.mean() + 2 * df.std(), 4)
    upl_3 = round(df.mean() + 3 * df.std(), 4)
    lowl_1 = round(df.mean() - df.std(), 4)
    lowl_2 = round(df.mean() - 2 * df.std(), 4)
    lowl_3 = round(df.mean() - 3 * df.std(), 4)
    result = f"""1 std limits = ({lowl_1}, {upl_1})
2 std limits = ({lowl_2}, {upl_2})
3 std limits = ({lowl_3}, {upl_3})
    """
    print(result)

def quartiles(data, base=per, show=False):
    """
    default show = False

    Return Q1, Q2, Q3, IQR
    """
    Q1 = base.percentile(data, 25)
    Q2 = base.percentile(data, 50)
    Q3 = base.percentile(data, 75)
    IQR = Q3 - Q1  # IQR is interquartile range.
    description = """
Q1 = {}
Q2 = {}
Q3 = {}
IQR = {}
    """.format(Q1, Q2, Q3, IQR)
    if show:
        print(description)

    return Q1, Q2, Q3, IQR

def outlier(data, base=per, show=True):
    """
    The default is set to per.percentile()
    default show = True

    Return a list of outliers and description

    Usages
    ------
    >>> df = whatever DataFrame you construct
    # to use per.percentile (textbook) to compute quartiles
    >>> per.outlier(df["column_name"], per)
    # to use np.percentile to compute quartiles
    >>> per.outlier(df["column_name"], np)

    Examples
    ------
    In HW04, 4.73, the results could be different:
    >>> per.outlier(df["Time_Public"].dropna(), np)
    Quartiles for playing on a public course:

    Q1 = 279.5
    Q2 = 296.0
    Q3 = 307.0
    IQR = 27.5

    Outliers are listed as follows:
    [238.0, 359.0]

    >>> per.outlier(df["Time_Public"].dropna()) # default = per
    Quartiles for playing on a public course:

    Q1 = 279.0
    Q2 = 296.0
    Q3 = 307.0
    IQR = 28.0

    Outliers are listed as follows:
    [359.0]

    """
    # Q1 = base.percentile(data, 25)
    # Q2 = base.percentile(data, 50)
    # Q3 = base.percentile(data, 75)
    # IQR = Q3 - Q1  # IQR is interquartile range.
    Q1, Q2, Q3, IQR = quartiles(data, base=base)
    filter = (data < Q1 - 1.5 * IQR) | (data > Q3 + 1.5 * IQR)
    if (len(data.loc[filter].to_list()) != 0):
        outlier_prompt = "Outliers are listed as follows:\n{}".format(
            data.loc[filter].to_list())
    else:
        outlier_prompt = "There are no outliers."

    description = """
Q1 = {}
Q2 = {}
Q3 = {}
IQR = {}

{}
    """.format(Q1, Q2, Q3, IQR, outlier_prompt)
    if show:
        print(description)

    if (len(data.loc[filter].to_list()) != 0):
        return data.loc[filter].to_list(), description
    else:
        return list(), description

def kurtosis(df):
    """
    How to read the kurtosis value?

    K > 3 : Leptokurtic (Narrow-tall)
    K = 3 : Mesokurtic (Regular)
    K < 3 : Platykurtic (Wide-low)
    -------
    To convert excel kurtosis to a real one, please consider the function "convert_excel_kurtosis(K, n)"

    """
    m2 = moment(df, moment=2)
    m4 = moment(df, moment=4)
    kurtosis_f = m4 / pow(m2, 2)
    return kurtosis_f

def convert_excel_kurtosis(K, n):
    """
    K is the kurtosis you get from Excel.

    The function will return (n-2) * (n-3) * K / ((n + 1) * (n - 1)) + 3 * (n - 1) / (n + 1).
    """
    if (n < 3):
        return None
    return (n-2) * (n-3) * K / ((n + 1) * (n - 1)) + 3 * (n - 1) / (n + 1)

def skew(df):
    """
    How to read skew value?

    g > 0 : Skewed to right
    g = 0 : Symmetric
    g < 0 : Skewed to left
    -------
    To convert excel skewness to a real one, please consider the function "convert_excel_skew(G, n)"
    """
    m2 = moment(df, moment=2)
    m3 = moment(df, moment=3)
    skew_f = m3 / pow(pow(m2, 0.5), 3)
    return skew_f

def convert_excel_skew(G, n):
    """
    G is the skewness you get from Excel.

    The function will return (n-2) * G / math.sqrt(n * (n - 1)).
    """
    if (n < 2):
        return None
    return (n-2) * G / math.sqrt(n * (n - 1))