# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/01_APIS/03_utils.ipynb.

# %% auto 0
__all__ = ['divide_on_feature', 'get_sorted_cats', 'calculate_entropy', 'mean_squared_error', 'calculate_variance',
           'calculate_std_dev', 'euclidean_distance', 'accuracy_score', 'calculate_covariance_matrix',
           'calculate_correlation_matrix', 'linear_kernel', 'polynomial_kernel', 'rbf_kernel']

# %% ../nbs/01_APIS/03_utils.ipynb 3
from fastcore.utils import *
import numpy as np
import pandas as pd
import gc
import numba



# %% ../nbs/01_APIS/03_utils.ipynb 5
def divide_on_feature(X, feature_i, threshold):
    """ Divide dataset based on if sample value on feature index is larger than
        the given threshold """
    split_func = None
    if isinstance(threshold, int) or isinstance(threshold, float):
        split_func = lambda sample: sample[feature_i] >= threshold
    elif isinstance(threshold,list):
        split_func = lambda sample: sample[feature_i].isin(threshold)
    else:
        split_func = lambda sample: sample[feature_i] == threshold

    # X_1 = np.array([sample for sample in X if split_func(sample)])
    # X_2 = np.array([sample for sample in X if not split_func(sample)])
    # return np.array([X_1, X_2])

    mask = split_func(X)
    X_1 = X[mask]
    X_2 = X[~mask]
    return X_1,X_2
    
def get_sorted_cats(x,ascending=True):
    u, count = np.unique(x, return_counts=True)
    if ascending:
        count_sort_ind = np.argsort(count)
    else:
        count_sort_ind = np.argsort(-count)
    return u[count_sort_ind]


def calculate_entropy(y):
    """ Calculate the entropy of label array y """
    log2 = lambda x: math.log(x) / math.log(2)
    unique_labels = np.unique(y)
    entropy = 0
    for label in unique_labels:
        count = len(y[y == label])
        p = count / len(y)
        entropy += -p * log2(p)
    return entropy


def mean_squared_error(y_true, y_pred):
    """ Returns the mean squared error between y_true and y_pred """
    mse = np.mean(np.power(y_true - y_pred, 2))
    return mse


def calculate_variance(X):
    """ Return the variance of the features in dataset X """
    mean = np.ones(np.shape(X)) * X.mean(0)
    n_samples = np.shape(X)[0]
    variance = (1 / n_samples) * np.diag((X - mean).T.dot(X - mean))
    
    return variance


def calculate_std_dev(X):
    """ Calculate the standard deviations of the features in dataset X """
    std_dev = np.sqrt(calculate_variance(X))
    return std_dev


def euclidean_distance(x1, x2):
    """ Calculates the l2 distance between two vectors """
    distance = 0
    # Squared distance between each coordinate
    for i in range(len(x1)):
        distance += pow((x1[i] - x2[i]), 2)
    return math.sqrt(distance)


def accuracy_score(y_true, y_pred):
    """ Compare y_true to y_pred and return the accuracy """
    accuracy = np.sum(y_true == y_pred, axis=0) / len(y_true)
    return accuracy


def calculate_covariance_matrix(X, Y=None):
    """ Calculate the covariance matrix for the dataset X """
    if Y is None:
        Y = X
    n_samples = np.shape(X)[0]
    covariance_matrix = (1 / (n_samples-1)) * (X - X.mean(axis=0)).T.dot(Y - Y.mean(axis=0))

    return np.array(covariance_matrix, dtype=float)
 

def calculate_correlation_matrix(X, Y=None):
    """ Calculate the correlation matrix for the dataset X """
    if Y is None:
        Y = X
    n_samples = np.shape(X)[0]
    covariance = (1 / n_samples) * (X - X.mean(0)).T.dot(Y - Y.mean(0))
    std_dev_X = np.expand_dims(calculate_std_dev(X), 1)
    std_dev_y = np.expand_dims(calculate_std_dev(Y), 1)
    correlation_matrix = np.divide(covariance, std_dev_X.dot(std_dev_y.T))

    return np.array(correlation_matrix, dtype=float)

def linear_kernel(**kwargs):
    def f(x1, x2):
        return np.inner(x1, x2)
    return f


def polynomial_kernel(power, coef, **kwargs):
    def f(x1, x2):
        return (np.inner(x1, x2) + coef)**power
    return f


def rbf_kernel(gamma, **kwargs):
    def f(x1, x2):
        distance = np.linalg.norm(x1 - x2) ** 2
        return np.exp(-gamma * distance)
    return f
