# Source code for skcriteria.plot

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

# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:

# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.

# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.

# * Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.

# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

# =============================================================================
# FUTURE & DOCS
# =============================================================================

from __future__ import unicode_literals

__doc__ = """Plotting utilities"""

__all__ = ["DataPlotMethods"]

# =============================================================================
# IMPORTS
# =============================================================================

import sys

import six

import numpy as np

from six.moves import zip

from matplotlib import cm

from ..validate import MIN, CRITERIA_STR
from .. import norm

from .multihist import multihist_plot
from .scmtx import scmtx_plot
from .box import box_plot
from .violin import violin_plot
from .bars import bars_plot

# =============================================================================
# DECORATOR
# =============================================================================

_plot_types = set()

def _plot_type(method):
return method

# =============================================================================
# CLASS
# =============================================================================

[docs]class DataPlotMethods(object):
"""Data plotting accessor and method

Examples
--------
>>> data.plot()
>>> data.plot.hist()
>>> data.plot.scatter('x', 'y')

These plotting methods can also be accessed by calling the accessor as a
method with the kind argument:
data.plot(kind='violin') is equivalent to data.plot.violin()

"""
def __init__(self, data):
self._data = data

def __unicode__(self):
return self.to_str()

def __bytes__(self):
encoding = sys.getdefaultencoding()
return self.__unicode__().encode(encoding, 'replace')

def __str__(self):
"""Return a string representation for a particular Object

Invoked by str(df) in both py2/py3.
Yields Bytestring in Py2, Unicode String in py3.
"""
if six.PY3:
return self.__unicode__()
return self.__bytes__()

def __repr__(self):
return str(self)

"""Make plots of Data using matplotlib / pylab."""

if kind not in _plot_types:
msg = "Invalid kind '{}'. Chooce from: {}"
raise ValueError(msg.format(kind, ", ".join(_plot_types)))
method = getattr(self, kind)
return method(**kwargs)

def to_str(self):
return "DataPlotMethods for {}".format(self._data)

[docs]    def preprocess(self, data, mnorm, wnorm,
anames, cnames, cmap,
weighted, show_criteria, min2max,
"""Preprocess the data to be plotted.

Parameters
----------
data : skcritria.core.Data
The data to be preprocessed.
mnorm: string, callable
Normalization method for the alternative matrix.
wnorm : string, callable
Normalization method for the weights array.
anames : list of str or None
The list of alternative names to be render in the plot.
If is None then the alternative names of data are used.
cnames : list of str or None
The list of criteria names to be render in the plot.
If is None then the criteria names of data are used.
cmap : string or None
Name of the color map to be used [1]_
weighted : bool
If the data must be weighted before redering.
show_criteria : bool
I the sense of optimality must be rendered in the plot.
min2max : bool
If true all the data of the minimization criteria are inverted
before render.
push_negatives : bool
If True all the criterias with some value < 0 are incremented to
be at least 0 in the minimun value.
If true add an small value to all the zeros inside the data.

Returns
-------
preprocessed_data : dict
All the data ready to be sended to a plot function

References
----------
.. [1] https://matplotlib.org/users/colormaps.html

"""

# extract all the data
mtx = data.mtx
criteria = data.criteria
weights = data.weights
anames = anames or data.anames
cnames = cnames or data.cnames

# push negatives
if push_negatives:
mtx = norm.push_negatives(mtx, axis=0)

# prevent zeroes

# convert all minimun criteria to max
if min2max:
mincrits = np.squeeze(np.where(criteria == MIN))
if np.any(mincrits):
mincrits_inverted = 1.0 / mtx[:, mincrits]
mtx = mtx.astype(mincrits_inverted.dtype.type)
mtx[:, mincrits] = mincrits_inverted

# normalization
mtx = norm.norm(mnorm, mtx, criteria=criteria, axis=0)
weights = (
norm.norm(wnorm, weights, criteria=criteria)
if weights is not None else None)

# weight the data
if weighted and weights is not None:
mtx = np.multiply(mtx, weights)

# labels for criteria
criterias = (
[" ({})".format(CRITERIA_STR[c]) for c in criteria]
if show_criteria else
[""] * len(criteria))

if weights is not None:
cnames = [
"{}{}\n(w.{:.2f})".format(cn, cr, cw)
for cn, cr, cw in zip(cnames, criterias, weights)]
else:
cnames = [
"{}{}".format(cn, cr) for cn, cr in zip(cnames, criterias)]

return {
"mtx": mtx, "criteria": criteria, "weights": weights,
"cmap": cm.get_cmap(name=cmap),
"anames": anames, "cnames": cnames}

[docs]    def plot(self, func, mnorm="none", wnorm="none",
anames=None, cnames=None, cmap=None,
weighted=True, show_criteria=True, min2max=False,
"""Preprocess the data and send to the plot function *func*.

Parameters
----------
func : callable
The function that make the plot. The return value of func
are the recutn value of this method.
mnorm: string, callable, optional (default="none")
Normalization method for the alternative matrix.
wnorm : string, callable, optional (default="none")
Normalization method for the weights array.
anames : list of str or None, optional (default=None)
The list of alternative names to be render in the plot.
If is None then the alternative names of data are used.
cnames : list of str or None, optional (default=None)
The list of criteria names to be render in the plot.
If is None then the criteria names of data are used.
cmap : string or None, optional (default=None)
Name of the color map to be used [1]_
weighted : bool, optional (default=True)
If the data must be weighted before redering.
show_criteria : bool, optional (default=True)
I the sense of optimality must be rendered in the plot.
min2max : bool, optional (default=False)
If true all the data of the minimization criteria are inverted
before render.
push_negatives : bool, optional (default=False)
If True all the criterias with some value < 0 are incremented to
be at least 0 in the minimun value.
If true add an small value to all the zeros inside the data.
kwargs :
Arguments to send to *func*

Returns
-------
The return value of *func*.

Notes
-----
All the plot methods of Scikit-Criteria returns a matplotlib axis.

References
----------
.. [1] https://matplotlib.org/users/colormaps.html

"""

ppkwargs = self.preprocess(
data=self._data, mnorm=mnorm, wnorm=wnorm,
anames=anames, cnames=cnames, cmap=cmap,
weighted=weighted, show_criteria=show_criteria,
push_negatives=push_negatives)
kwargs.update(ppkwargs)
return func(**kwargs)

[docs]    @_plot_type
"""Creates a radar chart, also known as a spider or star chart [1]_.

A radar chart is a graphical method of displaying multivariate data in
the form of a two-dimensional chart of three or more quantitative
variables represented on axes starting from the same point. The
relative position and angle of the axes is typically uninformative.

Parameters
----------
frame : {"polygon", "circle"}
Shape of frame surrounding axes.
ax : None or PolarAxes, optional (default=None)
Axis where the radar must be redered. Is is None a new axis are
created.
legendcol : int, optional (default=5)
How many columns must has the legend.
subplots_kwargs : dict or None, optional (default=None)
Argument to send to matplotlib.pyplot.subplots if axis is None.
If axis is not None, subplots_kwargs are ignored.

Returns
-------
ax : matplotlib.projections.polar.PolarAxes
Axis where the radar are rendered

--------
DataPlotMethods.plot : To check all the available parameters

Notes
-----
All the parameters in plot() are supported; but by default
this method override some default values:

- show_criteria=False
- min2max=True
- push_negatices=True
- addepsto0=True

References
----------

"""
kwargs.setdefault("show_criteria", False)
kwargs.setdefault("min2max", True)
kwargs.setdefault("push_negatives", True)

@_plot_type
def hist(self, **kwargs):
return self.plot(multihist_plot, **kwargs)

@_plot_type
def scatter(self, **kwargs):
return self.plot(scmtx_plot, **kwargs)

@_plot_type
def box(self, **kwargs):
return self.plot(box_plot, **kwargs)

@_plot_type
def violin(self, **kwargs):
return self.plot(violin_plot, **kwargs)

@_plot_type
def bars(self, **kwargs):
self.plot(bars_plot, **kwargs)