"""Functions for univariate analysis.

Most of these functions already have convenient implementations. The primary
purpose of this module is to provide a consistent syntax in a separate
namespace.

Always use qualified imports since this module contains a few names that shadow
built-in names.

Central tendency (numeric features)
------------------------------------
    mean(feature)
    median(feature)
    mode(feature)
    min(feature)
    max(feature)

Measures of dispersion (numeric features)
------------------------------------------
    range(feature)
    quartiles(feature)
    iqr(feature)
    var(feature)
    std(feature)
    skewness(feature)
    kurtosis(feature)

Categorical
-----------
    frequency_table(feature)

Visualizations
--------------
    histogram(feature)
    distribution(feature)
    box_plot(feature)
    bar_plot(feature)
"""

import seaborn as sns
import matplotlib.pyplot as plt


# Central tendency

def mean(feature):
    """Compute the mean value of a numeric feature.

    Parameters
    ----------
    feature : Union[:obj: `pandas.Series`,
                   :obj: `epysteme.Series`]

    Returns
    -------
    float
    """
    return feature.mean()


def median(feature):
    """Find the median of a numeric variable.

    Parameters
    ----------
    feature : Union[:obj: `pandas.Series`,
                   :obj: `epysteme.Series`]

    Returns
    -------
    float
    """
    return feature.median()


def mode(feature):
    """Find the mode(s) of a variable.

    Parameters
    ----------
    feature : Union[:obj: `pandas.Series`,
                   :obj: `epysteme.Series`]

    Returns
    -------
    [value]
    """
    return list(feature.mode().values)


def min(feature):
    """Find the minimum value of a numeric variable.

    Parameters
    ----------
    feature : Union[:obj: `pandas.Series`,
                   :obj: `epysteme.Series`]

    Returns
    -------
    float
    """
    return feature.min()


def max(feature):
    """Find the maximum value of a numeric variable.

    Parameters
    ----------
    feature : Union[:obj: `pandas.Series`,
                   :obj: `epysteme.Series`]

    Returns
    -------
    float
    """
    return feature.max()


# Measures of dispersion

def range(feature):
    """Compute the range of a numeric variable.

    Parameters
    ----------
    feature : Union[:obj: `pandas.Series`,
                   :obj: `epysteme.Series`]

    Returns
    -------
    float
    """
    return max(feature) - min(feature)


def quartiles(feature):
    """Compute the quartiles of a numeric variable.

    Parameters
    ----------
    feature : Union[:obj: `pandas.Series`,
                   :obj: `epysteme.Series`]

    Returns
    -------
    [float]
    """
    return [feature.quantile(0.25),
            feature.quantile(0.5),
            feature.quantile(0.75)]


def iqr(feature):
    """Compute the inter-quartile range of a numeric variable.

    Parameters
    ----------
    feature : Union[:obj: `pandas.Series`,
                   :obj: `epysteme.Series`]

    Returns
    -------
    float
    """
    return feature.quantile(0.75) - feature.quantile(0.25)


def var(feature):
    """Compute the variance of a numeric variable.

    Parameters
    ----------
    feature : Union[:obj: `pandas.Series`,
                   :obj: `epysteme.Series`]

    Returns
    -------
    float
    """
    return feature.var()


def std(feature):
    """Compute the standard deviation of a numeric variable.

    Parameters
    ----------
    feature : Union[:obj: `pandas.Series`,
                   :obj: `epysteme.Series`]

    Returns
    -------
    float
    """
    return feature.std()


def skewness(feature):
    """Compute the skewness of a numeric variable.

    Parameters
    ----------
    feature : Union[:obj: `pandas.Series`,
                   :obj: `epysteme.Series`]

    Returns
    -------
    float
    """
    return feature.skew()


def kurtosis(feature):
    """Compute the kurtosis of a numeric variable.

    Parameters
    ----------
    feature : Union[:obj: `pandas.Series`,
                   :obj: `epysteme.Series`]

    Returns
    -------
    float
    """
    return feature.kurtosis()


# All univariate statistics

def numeric_statistics(feature):
    """Compute all univariate statistics for a numeric variable.

    Parameters
    ----------
    feature : Union[:obj: `pandas.Series`,
                   :obj: `epysteme.Series`]

    Returns
    -------
    [(String, value)]
    """
    return [("mean", mean(feature)),
            ("median", median(feature)),
            ("mode", mode(feature)),
            ("min", min(feature)),
            ("max", max(feature)),
            ("range", range(feature)),
            ("quartiles", quartiles(feature)),
            ("iqr", iqr(feature)),
            ("variance", var(feature)),
            ("standard deviation", std(feature))]


# Categorical

def frequency_table(feature):
    """Compute a frequency table for a categorical variable.

    Parameters
    ----------
    feature : Union[:obj: `pandas.Series`,
                   :obj: `epysteme.Series`]

    Returns
    -------
    [(value, int, float)]
        List of (value, count, percentage) triples for each unique value the
        variable takes
    """
    n = feature.size
    return [(value,
             feature[feature == value].size,
             feature[feature == value].size/n) for value in feature.unique()]


# Visualizations

def histogram(feature):
    """Generate a histogram for a numeric variable.

    Parameters
    ----------
    feature : Union[:obj: `pandas.Series`,
                   :obj: `epysteme.Series`]
    """
    plt.figure()
    sns.distplot(feature.dropna(), kde=False)


def distribution(feature):
    """Generate a distribution plot for a numeric variable.

    Parameters
    ----------
    feature : Union[:obj: `pandas.Series`,
                   :obj: `epysteme.Series`]
    """
    plt.figure()
    sns.distplot(feature.dropna())


def box_plot(feature):
    """Generate a box plot for a numeric variable.

    Parameters
    ----------
    feature : Union[:obj: `pandas.Series`,
                   :obj: `epysteme.Series`]
    """
    plt.figure()
    sns.boxplot(feature)


def bar_plot(feature):
    """Generate a bar plot for a categorical variable.

    Parameters
    ----------
    feature : Union[:obj: `pandas.Series`,
                   :obj: `epysteme.Series`]
    """
    plt.figure()
    sns.barplot(x=[value for value in feature.unique()],
                y=[feature[feature == value].size
                   for value in feature.unique()])
    plt.title(feature.name)
